Compare commits

...

11 Commits

Author SHA1 Message Date
Tran Vinh Quang
fbf39e4932 Update vi.json (#1057) 2020-08-31 15:23:02 -05:00
Ian Lapham
975570fa97 improvement(swap): progress bar and more minimal default UI, also fix custom add/remove (#1069)
* add progress bar and minimal UI updates on swap

* add hook to explicity check user added tokens, fixes add/remove bug

* update with latest

* remove confusing comment

* update styles on loading, update arrow placement, code cleanup

* fix typo on progress import
2020-08-31 15:27:30 -04:00
Moody Salem
d6aa0e98a4 chore(token lists): replace aave token list with ens name 2020-08-28 10:52:21 -05:00
Ian Lapham
4644cd7b0a update missing logo icon (#1070) 2020-08-27 20:49:28 -04:00
Moody Salem
9ddedd8dab fix(pending approves): pending approves that are too old should not cause 'approving' to get stuck 2020-08-27 15:34:04 -05:00
Moody Salem
95030a52c5 fix(remove liquidity): price display in remove liquidity incorrect 2020-08-27 14:26:23 -05:00
Moody Salem
1911f72536 improvement(swap): show better trade link if trade doesn't exist on one version 2020-08-27 13:25:25 -05:00
Moody Salem
85217452db improvement(ts): strict everywhere 2020-08-27 13:10:00 -05:00
Moody Salem
f7a1a2ab58 move noImplicitAny and some type declarations 2020-08-27 12:24:03 -05:00
Moody Salem
66a2006284 more strictness everywhere, fix a pair pricing issue in mint/hooks.ts 2020-08-27 12:05:09 -05:00
Moody Salem
610b7f4464 make integration tests pass more reliably, some reducer refactoring 2020-08-27 10:21:51 -05:00
102 changed files with 556 additions and 500 deletions

View File

@@ -1,6 +1,5 @@
describe('Swap', () => {
beforeEach(() => {
cy.clearLocalStorage()
cy.visit('/swap')
})
@@ -13,8 +12,7 @@ describe('Swap', () => {
cy.get('#list-introduction-choose-a-list').should('not.exist')
})
// for some reason local storage is not being properly cleared
it.skip('change list', () => {
it('change list', () => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('#list-introduction-choose-a-list').click()
cy.get('#list-row-tokens-uniswap-eth .select-button').click()

View File

@@ -1,6 +1,5 @@
describe('Swap', () => {
beforeEach(() => {
cy.clearLocalStorage()
cy.visit('/swap')
})
it('can enter an amount into input', () => {
@@ -59,6 +58,7 @@ describe('Swap', () => {
cy.get('#toggle-expert-mode-button').click()
cy.get('#confirm-expert-mode').click()
})
it('add a recipient is visible', () => {
cy.get('#add-recipient-button').should('be.visible')
})

View File

@@ -1,11 +1,12 @@
describe('Warning', () => {
beforeEach(() => {
cy.clearLocalStorage()
cy.visit('/swap?outputCurrency=0x0a40f26d74274b7f22b28556a27b35d97ce08e0a')
})
it('Check that warning is displayed', () => {
cy.get('.token-warning-container').should('be.visible')
})
it('Check that warning hides after button dismissal', () => {
cy.get('.token-dismiss-button').should('be.disabled')
cy.get('.understand-checkbox').click()

View File

@@ -73,6 +73,7 @@ Cypress.Commands.overwrite('visit', (original, url, options) => {
...options,
onBeforeLoad(win) {
options && options.onBeforeLoad && options.onBeforeLoad(win)
win.localStorage.clear()
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
const signer = new Wallet(PRIVATE_KEY_TEST_NEVER_USE, provider)
win.ethereum = new CustomizedBridge(signer, provider)

View File

@@ -73,7 +73,7 @@
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.5",
"rebass": "^4.0.7",
"redux-localstorage-simple": "^2.2.0",
"redux-localstorage-simple": "^2.3.1",
"serve": "^11.3.0",
"start-server-and-test": "^1.11.0",
"styled-components": "^4.2.0",

View File

@@ -19,8 +19,8 @@
"pending": "Đang chờ xử lý",
"selectToken": "Chọn một đồng tiền ảo",
"searchOrPaste": "Tìm kiếm tên, biểu tượng, hoặc địa chỉ của đồng tiền ảo",
"searchOrPasteMobile": "Tên, Biểu tương, hoặc Địa chỉ",
"noExchange": "Không Tìm Thấy Giao Dịch",
"searchOrPasteMobile": "Tên, Biểu tưng, hoặc Địa chỉ",
"noExchange": "Không tìm thấy giao dịch",
"exchangeRate": "Tỷ giá",
"unknownError": "Rất tiếc! Xảy ra lỗi không xác định. Vui lòng làm mới trang, hoặc truy cập từ trình duyệt hay thiết bị khác.",
"enterValueCont": "Nhập một giá trị {{ missingCurrencyValue }} để tiếp tục.",
@@ -29,7 +29,7 @@
"insufficientLiquidity": "Không đủ tính thanh khoản.",
"unlockTokenCont": "Vui lòng mở khoá đồng tiền ảo để tiếp tục",
"transactionDetails": "Chi tiết nâng cao",
"hideDetails": "Che giấu chi tiết",
"hideDetails": "Ẩn chi tiết",
"slippageWarning": "Cảnh báo trượt giá",
"highSlippageWarning": "Cảnh báo trượt giá cao",
"youAreSelling": "Bạn đang bán",
@@ -59,7 +59,7 @@
"intoPool": "vào nhóm thanh khoản.",
"outPool": "từ nhóm thanh khoản.",
"youWillMint": "Bạn sẽ đúc tiền",
"liquidityTokens": "dồng thanh khoản.",
"liquidityTokens": "đồng thanh khoản.",
"totalSupplyIs": "Tổng cung hiện tại của đồng thanh khoản là",
"youAreSettingExRate": "Bạn đang đặt tỷ giá hối đoái ban đầu thành",
"totalSupplyIs0": "Tổng cung hiện tại của đồng thanh khoản là 0.",

View File

@@ -40,11 +40,12 @@ export default function Transaction({ hash }: { hash: string }) {
const { chainId } = useActiveWeb3React()
const allTransactions = useAllTransactions()
const summary = allTransactions?.[hash]?.summary
const pending = !allTransactions?.[hash]?.receipt
const success =
!pending &&
(allTransactions[hash].receipt.status === 1 || typeof allTransactions[hash].receipt.status === 'undefined')
const tx = allTransactions?.[hash]
const summary = tx?.summary
const pending = !tx?.receipt
const success = !pending && tx && (tx.receipt?.status === 1 || typeof tx.receipt?.status === 'undefined')
if (!chainId) return null
return (
<TransactionWrapper>

View File

@@ -200,7 +200,7 @@ const MainWalletAction = styled(WalletAction)`
color: ${({ theme }) => theme.primary1};
`
function renderTransactions(transactions) {
function renderTransactions(transactions: string[]) {
return (
<TransactionListWrapper>
{transactions.map((hash, i) => {
@@ -212,8 +212,8 @@ function renderTransactions(transactions) {
interface AccountDetailsProps {
toggleWalletModal: () => void
pendingTransactions: any[]
confirmedTransactions: any[]
pendingTransactions: string[]
confirmedTransactions: string[]
ENSName?: string
openOptions: () => void
}
@@ -282,15 +282,12 @@ export default function AccountDetails({
</>
)
}
return null
}
const clearAllTransactionsCallback = useCallback(
(event: React.MouseEvent) => {
event.preventDefault()
dispatch(clearAllTransactions({ chainId }))
},
[dispatch, chainId]
)
const clearAllTransactionsCallback = useCallback(() => {
if (chainId) dispatch(clearAllTransactions({ chainId }))
}, [dispatch, chainId])
return (
<>
@@ -338,7 +335,7 @@ export default function AccountDetails({
<>
<div>
{getStatusIcon()}
<p> {shortenAddress(account)}</p>
<p> {account && shortenAddress(account)}</p>
</div>
</>
)}
@@ -349,17 +346,21 @@ export default function AccountDetails({
<>
<AccountControl>
<div>
<Copy toCopy={account}>
<span style={{ marginLeft: '4px' }}>Copy Address</span>
</Copy>
<AddressLink
hasENS={!!ENSName}
isENS={true}
href={getEtherscanLink(chainId, ENSName, 'address')}
>
<LinkIcon size={16} />
<span style={{ marginLeft: '4px' }}>View on Etherscan</span>
</AddressLink>
{account && (
<Copy toCopy={account}>
<span style={{ marginLeft: '4px' }}>Copy Address</span>
</Copy>
)}
{chainId && account && (
<AddressLink
hasENS={!!ENSName}
isENS={true}
href={chainId && getEtherscanLink(chainId, ENSName, 'address')}
>
<LinkIcon size={16} />
<span style={{ marginLeft: '4px' }}>View on Etherscan</span>
</AddressLink>
)}
</div>
</AccountControl>
</>
@@ -367,17 +368,21 @@ export default function AccountDetails({
<>
<AccountControl>
<div>
<Copy toCopy={account}>
<span style={{ marginLeft: '4px' }}>Copy Address</span>
</Copy>
<AddressLink
hasENS={!!ENSName}
isENS={false}
href={getEtherscanLink(chainId, account, 'address')}
>
<LinkIcon size={16} />
<span style={{ marginLeft: '4px' }}>View on Etherscan</span>
</AddressLink>
{account && (
<Copy toCopy={account}>
<span style={{ marginLeft: '4px' }}>Copy Address</span>
</Copy>
)}
{chainId && account && (
<AddressLink
hasENS={!!ENSName}
isENS={false}
href={getEtherscanLink(chainId, account, 'address')}
>
<LinkIcon size={16} />
<span style={{ marginLeft: '4px' }}>View on Etherscan</span>
</AddressLink>
)}
</div>
</AccountControl>
</>

View File

@@ -65,11 +65,6 @@ const Input = styled.input<{ error?: boolean }>`
}
`
interface Value {
address: string
name?: string
}
export default function AddressInputPanel({
id,
value,
@@ -106,7 +101,7 @@ export default function AddressInputPanel({
<TYPE.black color={theme.text2} fontWeight={500} fontSize={14}>
Recipient
</TYPE.black>
{address && (
{address && chainId && (
<ExternalLink href={getEtherscanLink(chainId, name ?? address, 'address')} style={{ fontSize: '14px' }}>
(View on Etherscan)
</ExternalLink>

View File

@@ -10,7 +10,7 @@ const Base = styled(RebassButton)<{
padding?: string
width?: string
borderRadius?: string
altDisbaledStyle?: boolean
altDisabledStyle?: boolean
}>`
padding: ${({ padding }) => (padding ? padding : '18px')};
width: ${({ width }) => (width ? width : '100%')};
@@ -53,12 +53,13 @@ export const ButtonPrimary = styled(Base)`
background-color: ${({ theme }) => darken(0.1, theme.primary1)};
}
&:disabled {
background-color: ${({ theme, altDisbaledStyle }) => (altDisbaledStyle ? theme.primary1 : theme.bg3)};
color: ${({ theme, altDisbaledStyle }) => (altDisbaledStyle ? 'white' : theme.text3)};
background-color: ${({ theme, altDisabledStyle }) => (altDisabledStyle ? theme.primary1 : theme.bg3)};
color: ${({ theme, altDisabledStyle }) => (altDisabledStyle ? 'white' : theme.text3)};
cursor: auto;
box-shadow: none;
border: 1px solid transparent;
outline: none;
opacity: ${({ altDisabledStyle }) => (altDisabledStyle ? '0.7' : '1')};
}
`
@@ -253,11 +254,15 @@ const ButtonErrorStyle = styled(Base)`
}
`
export function ButtonConfirmed({ confirmed, ...rest }: { confirmed?: boolean } & ButtonProps) {
export function ButtonConfirmed({
confirmed,
altDisabledStyle,
...rest
}: { confirmed?: boolean; altDisabledStyle?: boolean } & ButtonProps) {
if (confirmed) {
return <ButtonConfirmedStyle {...rest} />
} else {
return <ButtonPrimary {...rest} />
return <ButtonPrimary {...rest} altDisabledStyle={altDisabledStyle} />
}
}

View File

@@ -137,13 +137,13 @@ export default function CurrencyInputPanel({
onMax,
showMaxButton,
label = 'Input',
onCurrencySelect = null,
currency = null,
onCurrencySelect,
currency,
disableCurrencySelect = false,
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
otherCurrency = null,
otherCurrency,
id,
showCommonBases
}: CurrencyInputPanelProps) {
@@ -151,7 +151,7 @@ export default function CurrencyInputPanel({
const [modalOpen, setModalOpen] = useState(false)
const { account } = useActiveWeb3React()
const selectedCurrencyBalance = useCurrencyBalance(account, currency)
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
const theme = useContext(ThemeContext)
const handleDismissSearch = useCallback(() => {
@@ -231,7 +231,7 @@ export default function CurrencyInputPanel({
</CurrencySelect>
</InputRow>
</Container>
{!disableCurrencySelect && (
{!disableCurrencySelect && onCurrencySelect && (
<CurrencySearchModal
isOpen={modalOpen}
onDismiss={handleDismissSearch}

View File

@@ -137,7 +137,7 @@ const NETWORK_LABELS: { [chainId in ChainId]: string | null } = {
export default function Header() {
const { account, chainId } = useActiveWeb3React()
const userEthBalance = useETHBalances([account])[account]
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
const [isDark] = useDarkModeManager()
return (
@@ -156,7 +156,7 @@ export default function Header() {
<HeaderControls>
<HeaderElement>
<TestnetWrapper>
{!isMobile && NETWORK_LABELS[chainId] && <NetworkCard>{NETWORK_LABELS[chainId]}</NetworkCard>}
{!isMobile && chainId && NETWORK_LABELS[chainId] && <NetworkCard>{NETWORK_LABELS[chainId]}</NetworkCard>}
</TestnetWrapper>
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
{account && userEthBalance ? (

View File

@@ -5,7 +5,7 @@ import styled from 'styled-components'
import { useActiveWeb3React } from '../../hooks'
import Jazzicon from 'jazzicon'
const StyledIdenticon = styled.div`
const StyledIdenticonContainer = styled.div`
height: 1rem;
width: 1rem;
border-radius: 1.125rem;
@@ -24,5 +24,6 @@ export default function Identicon() {
}
}, [account])
return <StyledIdenticon ref={ref} />
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
return <StyledIdenticonContainer ref={ref as any} />
}

View File

@@ -24,7 +24,7 @@ const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
* Takes in custom size and stroke for circle color, default to primary color as fill,
* need ...rest for layered styles on top
*/
export default function Loader({ size = '16px', stroke = null, ...rest }: { size?: string; stroke?: string }) {
export default function Loader({ size = '16px', stroke, ...rest }: { size?: string; stroke?: string }) {
return (
<StyledSVG viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" size={size} stroke={stroke} {...rest}>
<path

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'
import { AlertTriangle } from 'react-feather'
import { HelpCircle } from 'react-feather'
import { ImageProps } from 'rebass'
const BAD_SRCS: { [tokenAddress: string]: true } = {}
@@ -30,5 +30,5 @@ export default function Logo({ srcs, alt, ...rest }: LogoProps) {
)
}
return <AlertTriangle {...rest} />
return <HelpCircle {...rest} />
}

View File

@@ -87,7 +87,8 @@ export default function Menu() {
useOnClickOutside(node, open ? toggle : undefined)
return (
<StyledMenu ref={node}>
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
<StyledMenu ref={node as any}>
<StyledMenuButton onClick={toggle}>
<StyledMenuIcon />
</StyledMenuButton>

View File

@@ -86,7 +86,7 @@ export default function Modal({
onDismiss,
minHeight = false,
maxHeight = 50,
initialFocusRef = null,
initialFocusRef,
children
}: ModalProps) {
const fadeTransition = useTransition(isOpen, null, {

View File

@@ -46,7 +46,7 @@ export const Input = React.memo(function InnerInput({
...rest
}: {
value: string | number
onUserInput: (string) => void
onUserInput: (input: string) => void
error?: boolean
fontSize?: string
align?: 'right' | 'left'

View File

@@ -1,6 +1,6 @@
import { Placement } from '@popperjs/core'
import { transparentize } from 'polished'
import React, { useState } from 'react'
import React, { useCallback, useState } from 'react'
import { usePopper } from 'react-popper'
import styled from 'styled-components'
import useInterval from '../../hooks/useInterval'
@@ -83,9 +83,9 @@ export interface PopoverProps {
}
export default function Popover({ content, show, children, placement = 'auto' }: PopoverProps) {
const [referenceElement, setReferenceElement] = useState<HTMLDivElement>(null)
const [popperElement, setPopperElement] = useState<HTMLDivElement>(null)
const [arrowElement, setArrowElement] = useState<HTMLDivElement>(null)
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null)
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null)
const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null)
const { styles, update, attributes } = usePopper(referenceElement, popperElement, {
placement,
strategy: 'fixed',
@@ -94,17 +94,20 @@ export default function Popover({ content, show, children, placement = 'auto' }:
{ name: 'arrow', options: { element: arrowElement } }
]
})
useInterval(update, show ? 100 : null)
const updateCallback = useCallback(() => {
update && update()
}, [update])
useInterval(updateCallback, show ? 100 : null)
return (
<>
<ReferenceElement ref={setReferenceElement}>{children}</ReferenceElement>
<ReferenceElement ref={setReferenceElement as any}>{children}</ReferenceElement>
<Portal>
<PopoverContainer show={show} ref={setPopperElement} style={styles.popper} {...attributes.popper}>
<PopoverContainer show={show} ref={setPopperElement as any} style={styles.popper} {...attributes.popper}>
{content}
<Arrow
className={`arrow-${attributes.popper?.['data-popper-placement'] ?? ''}`}
ref={setArrowElement}
ref={setArrowElement as any}
style={styles.arrow}
{...attributes.arrow}
/>

View File

@@ -44,7 +44,8 @@ export default function ListUpdatePopup({
return diffTokenLists(oldList.tokens, newList.tokens)
}, [newList.tokens, oldList.tokens])
const numTokensChanged = useMemo(
() => Object.keys(tokensChanged).reduce((memo, chainId) => memo + Object.keys(tokensChanged[chainId]).length, 0),
() =>
Object.keys(tokensChanged).reduce((memo, chainId: any) => memo + Object.keys(tokensChanged[chainId]).length, 0),
[tokensChanged]
)

View File

@@ -55,7 +55,7 @@ export default function PopupItem({
const removePopup = useRemovePopup()
const removeThisPopup = useCallback(() => removePopup(popKey), [popKey, removePopup])
useEffect(() => {
if (removeAfterMs === null) return
if (removeAfterMs === null) return undefined
const timeout = setTimeout(() => {
removeThisPopup()
@@ -81,7 +81,11 @@ export default function PopupItem({
popupContent = <ListUpdatePopup popKey={popKey} listUrl={listUrl} oldList={oldList} newList={newList} auto={auto} />
}
const faderStyle = useSpring({ from: { width: '100%' }, to: { width: '0%' }, config: { duration: removeAfterMs } })
const faderStyle = useSpring({
from: { width: '100%' },
to: { width: '0%' },
config: { duration: removeAfterMs ?? undefined }
})
return (
<Popup>

View File

@@ -32,7 +32,9 @@ export default function TransactionPopup({
</div>
<AutoColumn gap="8px">
<TYPE.body fontWeight={500}>{summary ?? 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)}</TYPE.body>
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</ExternalLink>
{chainId && (
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</ExternalLink>
)}
</AutoColumn>
</RowNoFlex>
)

View File

@@ -28,7 +28,7 @@ function V1PositionCard({ token, V1LiquidityBalance }: PositionCardProps) {
<RowFixed>
<DoubleCurrencyLogo currency0={token} margin={true} size={20} />
<Text fontWeight={500} fontSize={20} style={{ marginLeft: '' }}>
{`${token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH`}
{`${chainId && token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH`}
</Text>
<Text
fontSize={12}

View File

@@ -46,7 +46,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
const [showMore, setShowMore] = useState(false)
const userPoolBalance = useTokenBalance(account, pair.liquidityToken)
const userPoolBalance = useTokenBalance(account ?? undefined, pair.liquidityToken)
const totalPoolTokens = useTotalSupply(pair.liquidityToken)
const [token0Deposited, token1Deposited] =
@@ -131,7 +131,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
const [showMore, setShowMore] = useState(false)
const userPoolBalance = useTokenBalance(account, pair.liquidityToken)
const userPoolBalance = useTokenBalance(account ?? undefined, pair.liquidityToken)
const totalPoolTokens = useTotalSupply(pair.liquidityToken)
const poolTokenPercentage =

View File

@@ -0,0 +1,79 @@
import React from 'react'
import styled from 'styled-components'
import { RowBetween } from '../Row'
import { AutoColumn } from '../Column'
import { transparentize } from 'polished'
const Wrapper = styled(AutoColumn)`
margin-top: 1.25rem;
`
const Grouping = styled(RowBetween)`
width: 50%;
`
const Circle = styled.div<{ confirmed?: boolean; disabled?: boolean }>`
min-width: 20px;
min-height: 20px;
background-color: ${({ theme, confirmed, disabled }) =>
disabled ? theme.bg4 : confirmed ? theme.green1 : theme.primary1};
border-radius: 50%;
color: ${({ theme }) => theme.white};
display: flex;
align-items: center;
justify-content: center;
line-height: 8px;
font-size: 12px;
`
const CircleRow = styled.div`
width: calc(100% - 20px);
display: flex;
align-items: center;
`
const Connector = styled.div<{ prevConfirmed?: boolean }>`
width: 100%;
height: 2px;
background-color: ;
background: linear-gradient(
90deg,
${({ theme, prevConfirmed }) => transparentize(0.5, prevConfirmed ? theme.green1 : theme.primary1)} 0%,
${({ theme, prevConfirmed }) => (prevConfirmed ? theme.primary1 : theme.bg4)} 80%
);
opacity: 0.6;
`
interface ProgressCirclesProps {
steps: boolean[]
}
/**
* Based on array of steps, create a step counter of circles.
* A circle can be enabled, disabled, or confirmed. States are derived
* from previous step.
*
* An extra circle is added to represent the ability to swap, add, or remove.
* This step will never be marked as complete (because no 'txn done' state in body ui).
*
* @param steps array of booleans where true means step is complete
*/
export default function ProgressCircles({ steps }: ProgressCirclesProps) {
return (
<Wrapper justify={'center'}>
<Grouping>
{steps.map((step, i) => {
return (
<CircleRow key={i}>
<Circle confirmed={step} disabled={!steps[i - 1] && i !== 0}>
{step ? '✓' : i + 1}
</Circle>
<Connector prevConfirmed={step} />
</CircleRow>
)
})}
<Circle disabled={!steps[steps.length - 1]}>{steps.length + 1}</Circle>
</Grouping>
</Wrapper>
)
}

View File

@@ -31,7 +31,7 @@ export default function CommonBases({
selectedCurrency
}: {
chainId?: ChainId
selectedCurrency?: Currency
selectedCurrency?: Currency | null
onSelect: (currency: Currency) => void
}) {
return (

View File

@@ -8,6 +8,7 @@ import { useSelectedTokenList, WrappedTokenInfo } from '../../state/lists/hooks'
import { useAddUserToken, useRemoveUserAddedToken } from '../../state/user/hooks'
import { useCurrencyBalance } from '../../state/wallet/hooks'
import { LinkStyledButton, TYPE } from '../../theme'
import { useIsUserAddedToken } from '../../hooks/Tokens'
import Column from '../Column'
import { RowFixed } from '../Row'
import CurrencyLogo from '../CurrencyLogo'
@@ -96,12 +97,13 @@ function CurrencyRow({
const key = currencyKey(currency)
const selectedTokenList = useSelectedTokenList()
const isOnSelectedList = isTokenOnList(selectedTokenList, currency)
const customAdded = Boolean(!isOnSelectedList && currency instanceof Token)
const customAdded = useIsUserAddedToken(currency)
const balance = useCurrencyBalance(account ?? undefined, currency)
const removeToken = useRemoveUserAddedToken()
const addToken = useAddUserToken()
// only show add or remove buttons if not on selected list
return (
<MenuItem
style={style}
@@ -116,7 +118,7 @@ function CurrencyRow({
{currency.symbol}
</Text>
<FadedSpan>
{customAdded ? (
{!isOnSelectedList && customAdded ? (
<TYPE.main fontWeight={500}>
Added by user
<LinkStyledButton
@@ -163,9 +165,9 @@ export default function CurrencyList({
}: {
height: number
currencies: Currency[]
selectedCurrency: Currency | undefined
selectedCurrency?: Currency | null
onCurrencySelect: (currency: Currency) => void
otherCurrency: Currency | undefined
otherCurrency?: Currency | null
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
showETH: boolean
}) {

View File

@@ -26,9 +26,9 @@ import AutoSizer from 'react-virtualized-auto-sizer'
interface CurrencySearchProps {
isOpen: boolean
onDismiss: () => void
selectedCurrency?: Currency
selectedCurrency?: Currency | null
onCurrencySelect: (currency: Currency) => void
otherSelectedCurrency?: Currency
otherSelectedCurrency?: Currency | null
showCommonBases?: boolean
onChangeList: () => void
}

View File

@@ -11,9 +11,9 @@ import { ListSelect } from './ListSelect'
interface CurrencySearchModalProps {
isOpen: boolean
onDismiss: () => void
selectedCurrency?: Currency
selectedCurrency?: Currency | null
onCurrencySelect: (currency: Currency) => void
otherSelectedCurrency?: Currency
otherSelectedCurrency?: Currency | null
showCommonBases?: boolean
}

View File

@@ -31,6 +31,6 @@ export function filterTokens(tokens: Token[], search: string): Token[] {
return tokens.filter(token => {
const { symbol, name } = token
return matchesSearch(symbol) || matchesSearch(name)
return (symbol && matchesSearch(symbol)) || (name && matchesSearch(name))
})
}

View File

@@ -1,4 +0,0 @@
{
"extends": "../../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -141,7 +141,8 @@ export default function SettingsTab() {
useOnClickOutside(node, open ? toggle : undefined)
return (
<StyledMenu ref={node}>
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
<StyledMenu ref={node as any}>
<Modal isOpen={showConfirmation} onDismiss={() => setShowConfirmation(false)} maxHeight={100}>
<ModalContentWrapper>
<AutoColumn gap="lg">

View File

@@ -53,7 +53,7 @@ function TokenWarningCard({ token }: TokenWarningCardProps) {
if (userToken.equals(token)) {
return false
}
return userToken.symbol.toLowerCase() === tokenSymbol || userToken.name.toLowerCase() === tokenName
return userToken.symbol?.toLowerCase() === tokenSymbol || userToken.name?.toLowerCase() === tokenName
})
}, [token, chainId, allTokens, tokenSymbol, tokenName])
@@ -72,9 +72,11 @@ function TokenWarningCard({ token }: TokenWarningCardProps) {
? `${token.name} (${token.symbol})`
: token.name || token.symbol}{' '}
</TYPE.main>
<ExternalLink style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, token.address, 'token')}>
<TYPE.blue title={token.address}>{shortenAddress(token.address)} (View on Etherscan)</TYPE.blue>
</ExternalLink>
{chainId && (
<ExternalLink style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, token.address, 'token')}>
<TYPE.blue title={token.address}>{shortenAddress(token.address)} (View on Etherscan)</TYPE.blue>
</ExternalLink>
)}
</AutoColumn>
</AutoRow>
</Wrapper>

View File

@@ -91,11 +91,13 @@ function TransactionSubmittedContent({
Transaction Submitted
</Text>
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>
<Text fontWeight={500} fontSize={14} color={theme.primary1}>
View on Etherscan
</Text>
</ExternalLink>
{chainId && hash && (
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>
<Text fontWeight={500} fontSize={14} color={theme.primary1}>
View on Etherscan
</Text>
</ExternalLink>
)}
<ButtonPrimary onClick={onDismiss} style={{ margin: '20px 0 0 0' }}>
<Text fontWeight={500} fontSize={20}>
Close

View File

@@ -1,4 +0,0 @@
{
"extends": "../../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -104,48 +104,44 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
slippageInput === '' || (rawSlippage / 100).toFixed(2) === Number.parseFloat(slippageInput).toFixed(2)
const deadlineInputIsValid = deadlineInput === '' || (deadline / 60).toString() === deadlineInput
let slippageError: SlippageError
let slippageError: SlippageError | undefined
if (slippageInput !== '' && !slippageInputIsValid) {
slippageError = SlippageError.InvalidInput
} else if (slippageInputIsValid && rawSlippage < 50) {
slippageError = SlippageError.RiskyLow
} else if (slippageInputIsValid && rawSlippage > 500) {
slippageError = SlippageError.RiskyHigh
} else {
slippageError = undefined
}
let deadlineError: DeadlineError
let deadlineError: DeadlineError | undefined
if (deadlineInput !== '' && !deadlineInputIsValid) {
deadlineError = DeadlineError.InvalidInput
} else {
deadlineError = undefined
}
function parseCustomSlippage(event) {
setSlippageInput(event.target.value)
function parseCustomSlippage(value: string) {
setSlippageInput(value)
let valueAsIntFromRoundedFloat: number
try {
valueAsIntFromRoundedFloat = Number.parseInt((Number.parseFloat(event.target.value) * 100).toString())
const valueAsIntFromRoundedFloat = Number.parseInt((Number.parseFloat(value) * 100).toString())
if (!Number.isNaN(valueAsIntFromRoundedFloat) && valueAsIntFromRoundedFloat < 5000) {
setRawSlippage(valueAsIntFromRoundedFloat)
}
} catch {}
if (
typeof valueAsIntFromRoundedFloat === 'number' &&
!Number.isNaN(valueAsIntFromRoundedFloat) &&
valueAsIntFromRoundedFloat < 5000
) {
setRawSlippage(valueAsIntFromRoundedFloat)
}
}
function parseCustomDeadline(event) {
setDeadlineInput(event.target.value)
function parseCustomDeadline(value: string) {
setDeadlineInput(value)
let valueAsInt: number
try {
valueAsInt = Number.parseInt(event.target.value) * 60
const valueAsInt: number = Number.parseInt(value) * 60
if (!Number.isNaN(valueAsInt) && valueAsInt > 0) {
setDeadline(valueAsInt)
}
} catch {}
if (typeof valueAsInt === 'number' && !Number.isNaN(valueAsInt) && valueAsInt > 0) {
setDeadline(valueAsInt)
}
}
return (
@@ -195,14 +191,15 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
</span>
</SlippageEmojiContainer>
) : null}
{/* https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 */}
<Input
ref={inputRef}
ref={inputRef as any}
placeholder={(rawSlippage / 100).toFixed(2)}
value={slippageInput}
onBlur={() => {
parseCustomSlippage({ target: { value: (rawSlippage / 100).toFixed(2) } })
parseCustomSlippage((rawSlippage / 100).toFixed(2))
}}
onChange={parseCustomSlippage}
onChange={e => parseCustomSlippage(e.target.value)}
color={!slippageInputIsValid ? 'red' : ''}
/>
%
@@ -238,11 +235,11 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
<Input
color={!!deadlineError ? 'red' : undefined}
onBlur={() => {
parseCustomDeadline({ target: { value: (deadline / 60).toString() } })
parseCustomDeadline((deadline / 60).toString())
}}
placeholder={(deadline / 60).toString()}
value={deadlineInput}
onChange={parseCustomDeadline}
onChange={e => parseCustomDeadline(e.target.value)}
/>
</OptionCustom>
<TYPE.body style={{ paddingLeft: '8px' }} fontSize={14}>

View File

@@ -73,7 +73,7 @@ const SubHeader = styled.div`
font-size: 12px;
`
const IconWrapper = styled.div<{ size?: number }>`
const IconWrapper = styled.div<{ size?: number | null }>`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
@@ -90,7 +90,7 @@ const IconWrapper = styled.div<{ size?: number }>`
export default function Option({
link = null,
clickable = true,
size = null,
size,
onClick = null,
color,
header,

View File

@@ -86,7 +86,7 @@ export default function PendingView({
<ErrorButton
onClick={() => {
setPendingError(false)
tryActivation(connector)
connector && tryActivation(connector)
}}
>
Try Again

View File

@@ -17,6 +17,7 @@ import { ReactComponent as Close } from '../../assets/images/x.svg'
import { injected, fortmatic, portis } from '../../connectors'
import { OVERLAY_READY } from '../../connectors/Fortmatic'
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
import { AbstractConnector } from '@web3-react/abstract-connector'
const CloseIcon = styled.div`
position: absolute;
@@ -128,7 +129,7 @@ export default function WalletModal({
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
const [pendingWallet, setPendingWallet] = useState()
const [pendingWallet, setPendingWallet] = useState<AbstractConnector | undefined>()
const [pendingError, setPendingError] = useState<boolean>()
@@ -161,7 +162,7 @@ export default function WalletModal({
}
}, [setWalletView, active, error, connector, walletModalOpen, activePrevious, connectorPrevious])
const tryActivation = async connector => {
const tryActivation = async (connector: AbstractConnector | undefined) => {
let name = ''
Object.keys(SUPPORTED_WALLETS).map(key => {
if (connector === SUPPORTED_WALLETS[key].connector) {
@@ -183,13 +184,14 @@ export default function WalletModal({
connector.walletConnectProvider = undefined
}
activate(connector, undefined, true).catch(error => {
if (error instanceof UnsupportedChainIdError) {
activate(connector) // a little janky...can't use setError because the connector isn't set
} else {
setPendingError(true)
}
})
connector &&
activate(connector, undefined, true).catch(error => {
if (error instanceof UnsupportedChainIdError) {
activate(connector) // a little janky...can't use setError because the connector isn't set
} else {
setPendingError(true)
}
})
}
// close wallet modal if fortmatic modal is active
@@ -358,7 +360,7 @@ export default function WalletModal({
}
return (
<Modal isOpen={walletModalOpen} onDismiss={toggleWalletModal} minHeight={null} maxHeight={90}>
<Modal isOpen={walletModalOpen} onDismiss={toggleWalletModal} minHeight={false} maxHeight={90}>
<Wrapper>{getModalContent()}</Wrapper>
</Modal>
)

View File

@@ -19,7 +19,7 @@ const Message = styled.h2`
color: ${({ theme }) => theme.secondary1};
`
export default function Web3ReactManager({ children }) {
export default function Web3ReactManager({ children }: { children: JSX.Element }) {
const { t } = useTranslation()
const { active } = useWeb3React()
const { active: networkActive, error: networkError, activate: activateNetwork } = useWeb3React(NetworkContextName)

View File

@@ -1,28 +1,29 @@
import React, { useMemo } from 'react'
import styled, { css } from 'styled-components'
import { useTranslation } from 'react-i18next'
import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core'
import { AbstractConnector } from '@web3-react/abstract-connector'
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core'
import { darken, lighten } from 'polished'
import React, { useMemo } from 'react'
import { Activity } from 'react-feather'
import { useTranslation } from 'react-i18next'
import styled, { css } from 'styled-components'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../../connectors'
import { NetworkContextName } from '../../constants'
import useENSName from '../../hooks/useENSName'
import { useHasSocks } from '../../hooks/useSocksBalance'
import { useWalletModalToggle } from '../../state/application/hooks'
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
import { TransactionDetails } from '../../state/transactions/reducer'
import { shortenAddress } from '../../utils'
import { ButtonSecondary } from '../Button'
import Identicon from '../Identicon'
import PortisIcon from '../../assets/images/portisIcon.png'
import WalletModal from '../WalletModal'
import { ButtonSecondary } from '../Button'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import Loader from '../Loader'
import { RowBetween } from '../Row'
import { shortenAddress } from '../../utils'
import { useAllTransactions } from '../../state/transactions/hooks'
import { NetworkContextName } from '../../constants'
import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors'
import Loader from '../Loader'
import WalletModal from '../WalletModal'
const IconWrapper = styled.div<{ size?: number }>`
${({ theme }) => theme.flexColumnNoWrap};
@@ -118,104 +119,114 @@ const NetworkIcon = styled(Activity)`
`
// we want the latest one to come first, so return negative if a is after b
function newTranscationsFirst(a: TransactionDetails, b: TransactionDetails) {
function newTransactionsFirst(a: TransactionDetails, b: TransactionDetails) {
return b.addedTime - a.addedTime
}
function recentTransactionsOnly(a: TransactionDetails) {
return new Date().getTime() - a.addedTime < 86_400_000
}
const SOCK = (
<span role="img" aria-label="has socks emoji" style={{ marginTop: -4, marginBottom: -4 }}>
🧦
</span>
)
export default function Web3Status() {
const { t } = useTranslation()
const { active, account, connector, error } = useWeb3React()
const contextNetwork = useWeb3React(NetworkContextName)
// eslint-disable-next-line react/prop-types
function StatusIcon({ connector }: { connector: AbstractConnector }) {
if (connector === injected) {
return <Identicon />
} else if (connector === walletconnect) {
return (
<IconWrapper size={16}>
<img src={WalletConnectIcon} alt={''} />
</IconWrapper>
)
} else if (connector === walletlink) {
return (
<IconWrapper size={16}>
<img src={CoinbaseWalletIcon} alt={''} />
</IconWrapper>
)
} else if (connector === fortmatic) {
return (
<IconWrapper size={16}>
<img src={FortmaticIcon} alt={''} />
</IconWrapper>
)
} else if (connector === portis) {
return (
<IconWrapper size={16}>
<img src={PortisIcon} alt={''} />
</IconWrapper>
)
}
return null
}
const { ENSName } = useENSName(account)
function Web3StatusInner() {
const { t } = useTranslation()
const { account, connector, error } = useWeb3React()
const { ENSName } = useENSName(account ?? undefined)
const allTransactions = useAllTransactions()
const sortedRecentTransactions = useMemo(() => {
const txs = Object.values(allTransactions)
return txs.filter(recentTransactionsOnly).sort(newTranscationsFirst)
return txs.filter(isTransactionRecent).sort(newTransactionsFirst)
}, [allTransactions])
const pending = sortedRecentTransactions.filter(tx => !tx.receipt).map(tx => tx.hash)
const confirmed = sortedRecentTransactions.filter(tx => tx.receipt).map(tx => tx.hash)
const hasPendingTransactions = !!pending.length
const hasSocks = useHasSocks()
const toggleWalletModal = useWalletModalToggle()
// handle the logo we want to show with the account
function getStatusIcon() {
if (connector === injected) {
return <Identicon />
} else if (connector === walletconnect) {
return (
<IconWrapper size={16}>
<img src={WalletConnectIcon} alt={''} />
</IconWrapper>
)
} else if (connector === walletlink) {
return (
<IconWrapper size={16}>
<img src={CoinbaseWalletIcon} alt={''} />
</IconWrapper>
)
} else if (connector === fortmatic) {
return (
<IconWrapper size={16}>
<img src={FortmaticIcon} alt={''} />
</IconWrapper>
)
} else if (connector === portis) {
return (
<IconWrapper size={16}>
<img src={PortisIcon} alt={''} />
</IconWrapper>
)
}
if (account) {
return (
<Web3StatusConnected id="web3-status-connected" onClick={toggleWalletModal} pending={hasPendingTransactions}>
{hasPendingTransactions ? (
<RowBetween>
<Text>{pending?.length} Pending</Text> <Loader stroke="white" />
</RowBetween>
) : (
<>
{hasSocks ? SOCK : null}
<Text>{ENSName || shortenAddress(account)}</Text>
</>
)}
{!hasPendingTransactions && connector && <StatusIcon connector={connector} />}
</Web3StatusConnected>
)
} else if (error) {
return (
<Web3StatusError onClick={toggleWalletModal}>
<NetworkIcon />
<Text>{error instanceof UnsupportedChainIdError ? 'Wrong Network' : 'Error'}</Text>
</Web3StatusError>
)
} else {
return (
<Web3StatusConnect id="connect-wallet" onClick={toggleWalletModal} faded={!account}>
<Text>{t('Connect to a wallet')}</Text>
</Web3StatusConnect>
)
}
}
function getWeb3Status() {
if (account) {
return (
<Web3StatusConnected id="web3-status-connected" onClick={toggleWalletModal} pending={hasPendingTransactions}>
{hasPendingTransactions ? (
<RowBetween>
<Text>{pending?.length} Pending</Text> <Loader stroke="white" />
</RowBetween>
) : (
<>
{hasSocks ? SOCK : null}
<Text>{ENSName || shortenAddress(account)}</Text>
</>
)}
{!hasPendingTransactions && getStatusIcon()}
</Web3StatusConnected>
)
} else if (error) {
return (
<Web3StatusError onClick={toggleWalletModal}>
<NetworkIcon />
<Text>{error instanceof UnsupportedChainIdError ? 'Wrong Network' : 'Error'}</Text>
</Web3StatusError>
)
} else {
return (
<Web3StatusConnect id="connect-wallet" onClick={toggleWalletModal} faded={!account}>
<Text>{t('Connect to a wallet')}</Text>
</Web3StatusConnect>
)
}
}
export default function Web3Status() {
const { active, account } = useWeb3React()
const contextNetwork = useWeb3React(NetworkContextName)
const { ENSName } = useENSName(account ?? undefined)
const allTransactions = useAllTransactions()
const sortedRecentTransactions = useMemo(() => {
const txs = Object.values(allTransactions)
return txs.filter(isTransactionRecent).sort(newTransactionsFirst)
}, [allTransactions])
const pending = sortedRecentTransactions.filter(tx => !tx.receipt).map(tx => tx.hash)
const confirmed = sortedRecentTransactions.filter(tx => tx.receipt).map(tx => tx.hash)
if (!contextNetwork.active && !active) {
return null
@@ -223,8 +234,8 @@ export default function Web3Status() {
return (
<>
{getWeb3Status()}
<WalletModal ENSName={ENSName} pendingTransactions={pending} confirmedTransactions={confirmed} />
<Web3StatusInner />
<WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
</>
)
}

View File

@@ -3,7 +3,7 @@ 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) {
export default function GoogleAnalyticsReporter({ location: { pathname, search } }: RouteComponentProps): null {
useEffect(() => {
ReactGA.pageview(`${pathname}${search}`)
}, [pathname, search])

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { Currency, Price } from '@uniswap/sdk'
import { Price } from '@uniswap/sdk'
import { useContext } from 'react'
import { Repeat } from 'react-feather'
import { Text } from 'rebass'
@@ -8,27 +8,19 @@ import { StyledBalanceMaxMini } from './styleds'
interface TradePriceProps {
price?: Price
inputCurrency?: Currency
outputCurrency?: Currency
showInverted: boolean
setShowInverted: (showInverted: boolean) => void
}
export default function TradePrice({
price,
inputCurrency,
outputCurrency,
showInverted,
setShowInverted
}: TradePriceProps) {
export default function TradePrice({ price, showInverted, setShowInverted }: TradePriceProps) {
const theme = useContext(ThemeContext)
const formattedPrice = showInverted ? price?.toSignificant(6) : price?.invert()?.toSignificant(6)
const show = Boolean(inputCurrency && outputCurrency)
const show = Boolean(price?.baseCurrency && price?.quoteCurrency)
const label = showInverted
? `${outputCurrency?.symbol} per ${inputCurrency?.symbol}`
: `${inputCurrency?.symbol} per ${outputCurrency?.symbol}`
? `${price?.quoteCurrency?.symbol} per ${price?.baseCurrency?.symbol}`
: `${price?.baseCurrency?.symbol} per ${price?.quoteCurrency?.symbol}`
return (
<Text

View File

@@ -1,4 +0,0 @@
{
"extends": "../../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -16,6 +16,7 @@ export class FortmaticConnector extends FortmaticConnectorCore {
async activate() {
if (!this.fortmatic) {
const { default: Fortmatic } = await import('fortmatic')
const { apiKey, chainId } = this as any
if (chainId in CHAIN_ID_NETWORK_ARGUMENT) {
this.fortmatic = new Fortmatic(apiKey, CHAIN_ID_NETWORK_ARGUMENT[chainId as FormaticSupportedChains])

View File

@@ -1,4 +0,0 @@
{
"extends": "../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -11,10 +11,10 @@ export const DEFAULT_LIST_OF_LISTS: string[] = [
'erc20.cmc.eth',
'stablecoin.cmc.eth',
'tokenlist.zerion.eth',
'tokenlist.aave.eth',
'https://www.coingecko.com/tokens_list/uniswap/defi_100/v_0_0_0.json',
'https://app.tryroll.com/tokens.json',
'https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json',
'ipfs://QmVNCFc3y1DMt8n4K42d8BYubUhQ7FgcNxzEHxSEHszUhL', // aave token list
'https://defiprime.com/defiprime.tokenlist.json',
'https://umaproject.org/uma.tokenlist.json'
]

View File

@@ -1,4 +0,0 @@
{
"extends": "../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -167,6 +167,8 @@ export function isTradeBetter(
tradeB: Trade | undefined,
minimumDelta: Percent = ZERO_PERCENT
): boolean | undefined {
if (tradeA && !tradeB) return false
if (tradeB && !tradeA) return true
if (!tradeA || !tradeB) return undefined
if (

View File

@@ -1,4 +0,0 @@
{
"extends": "../../tsconfig.strict.json",
"include": ["**/*"]
}

8
src/ethereum.d.ts vendored
View File

@@ -1,8 +0,0 @@
interface Window {
ethereum?: {
isMetaMask?: true
on?: (...args: any[]) => void
removeListener?: (...args: any[]) => void
}
web3?: {}
}

View File

@@ -1,5 +1,5 @@
import { parseBytes32String } from '@ethersproject/strings'
import { Currency, ETHER, Token } from '@uniswap/sdk'
import { Currency, ETHER, Token, currencyEquals } from '@uniswap/sdk'
import { useMemo } from 'react'
import { useSelectedTokenList } from '../state/lists/hooks'
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
@@ -32,6 +32,12 @@ export function useAllTokens(): { [address: string]: Token } {
}, [chainId, userAddedTokens, allTokens])
}
// Check if currency is included in custom list from user storage
export function useIsUserAddedToken(currency: Currency): boolean {
const userAddedTokens = useUserAddedTokens()
return !!userAddedTokens.find(token => currencyEquals(currency, token))
}
// parse a name or symbol from a token response
const BYTES32_REGEX = /^0x[a-fA-F0-9]{64}$/
function parseStringOrBytes32(str: string | undefined, bytes32: string | undefined, defaultValue: string): string {

View File

@@ -1,8 +0,0 @@
interface Window {
ethereum?: {
isMetaMask?: true
on?: (...args: any[]) => void
removeListener?: (...args: any[]) => void
}
web3?: {}
}

View File

@@ -82,6 +82,6 @@ export function useInactiveListener(suppress = false) {
}
}
}
return
return undefined
}, [active, error, suppress, activate])
}

View File

@@ -1,4 +0,0 @@
{
"extends": "../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -19,7 +19,7 @@ export default function useCopyClipboard(timeout = 500): [boolean, (toCopy: stri
clearTimeout(hide)
}
}
return
return undefined
}, [isCopied, setIsCopied, timeout])
return [isCopied, staticCopy]

View File

@@ -20,6 +20,6 @@ export default function useInterval(callback: () => void, delay: null | number,
const id = setInterval(tick, delay)
return () => clearInterval(id)
}
return
return undefined
}, [delay, leading])
}

View File

@@ -16,7 +16,7 @@ export default function useIsWindowVisible(): boolean {
}, [setFocused])
useEffect(() => {
if (!VISIBILITY_STATE_SUPPORTED) return
if (!VISIBILITY_STATE_SUPPORTED) return undefined
document.addEventListener('visibilitychange', listener)
return () => {

View File

@@ -1,7 +0,0 @@
{
"extends": "../../../tsconfig.strict.json",
"include": [
"**/*",
"../../../node_modules/eslint-plugin-react/lib/types.d.ts"
]
}

View File

@@ -67,14 +67,14 @@ export function V1LiquidityInfo({
<div style={{ marginLeft: '.75rem' }}>
<TYPE.mediumHeader>
{<FormattedPoolCurrencyAmount currencyAmount={liquidityTokenAmount} />}{' '}
{token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH
{chainId && token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH
</TYPE.mediumHeader>
</div>
</AutoRow>
<RowBetween my="1rem">
<Text fontSize={16} fontWeight={500}>
Pooled {token.equals(WETH[chainId]) ? 'WETH' : token.symbol}:
Pooled {chainId && token.equals(WETH[chainId]) ? 'WETH' : token.symbol}:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
@@ -107,7 +107,7 @@ function V1PairMigration({ liquidityTokenAmount, token }: { liquidityTokenAmount
const [v2PairState, v2Pair] = usePair(chainId ? WETH[chainId] : undefined, token)
const isFirstLiquidityProvider: boolean = v2PairState === PairState.NOT_EXISTS
const v2SpotPrice = v2Pair?.reserveOf(token)?.divide(v2Pair?.reserveOf(WETH[chainId]))
const v2SpotPrice = chainId && v2Pair ? v2Pair.reserveOf(token).divide(v2Pair.reserveOf(WETH[chainId])) : undefined
const [confirmingMigration, setConfirmingMigration] = useState<boolean>(false)
const [pendingMigrationHash, setPendingMigrationHash] = useState<string | null>(null)
@@ -158,11 +158,11 @@ function V1PairMigration({ liquidityTokenAmount, token }: { liquidityTokenAmount
: tokenWorth?.numerator
const addTransaction = useTransactionAdder()
const isMigrationPending = useIsTransactionPending(pendingMigrationHash)
const isMigrationPending = useIsTransactionPending(pendingMigrationHash ?? undefined)
const migrator = useV2MigratorContract()
const migrate = useCallback(() => {
if (!minAmountToken || !minAmountETH) return
if (!minAmountToken || !minAmountETH || !migrator) return
setConfirmingMigration(true)
migrator
@@ -194,16 +194,18 @@ function V1PairMigration({ liquidityTokenAmount, token }: { liquidityTokenAmount
const largePriceDifference = !!priceDifferenceAbs && !priceDifferenceAbs.lessThan(JSBI.BigInt(5))
const isSuccessfullyMigrated = !!pendingMigrationHash && !!noLiquidityTokens
const isSuccessfullyMigrated = !!pendingMigrationHash && noLiquidityTokens
return (
<AutoColumn gap="20px">
<TYPE.body my={9} style={{ fontWeight: 400 }}>
This tool will safely migrate your V1 liquidity to V2 with minimal price risk. The process is completely
trustless thanks to the{' '}
<ExternalLink href={getEtherscanLink(chainId, MIGRATOR_ADDRESS, 'address')}>
<TYPE.blue display="inline">Uniswap migration contract</TYPE.blue>
</ExternalLink>
{chainId && (
<ExternalLink href={getEtherscanLink(chainId, MIGRATOR_ADDRESS, 'address')}>
<TYPE.blue display="inline">Uniswap migration contract</TYPE.blue>
</ExternalLink>
)}
.
</TYPE.body>
@@ -242,7 +244,7 @@ function V1PairMigration({ liquidityTokenAmount, token }: { liquidityTokenAmount
<RowBetween>
<TYPE.body color="inherit">Price Difference:</TYPE.body>
<TYPE.black color="inherit">{priceDifferenceAbs.toSignificant(4)}%</TYPE.black>
<TYPE.black color="inherit">{priceDifferenceAbs?.toSignificant(4)}%</TYPE.black>
</RowBetween>
</AutoColumn>
</YellowCard>
@@ -336,12 +338,12 @@ export default function MigrateV1Exchange({
const liquidityToken: Token | undefined = useMemo(
() =>
validatedAddress && token
validatedAddress && chainId && token
? new Token(chainId, validatedAddress, 18, `UNI-V1-${token.symbol}`, 'Uniswap V1')
: undefined,
[chainId, validatedAddress, token]
)
const userLiquidityBalance = useTokenBalance(account, liquidityToken)
const userLiquidityBalance = useTokenBalance(account ?? undefined, liquidityToken)
// redirect for invalid url params
if (!validatedAddress || tokenAddress === AddressZero) {
@@ -362,7 +364,7 @@ export default function MigrateV1Exchange({
{!account ? (
<TYPE.largeHeader>You must connect an account.</TYPE.largeHeader>
) : validatedAddress && token?.equals(WETH[chainId]) ? (
) : validatedAddress && chainId && token?.equals(WETH[chainId]) ? (
<>
<TYPE.body my={9} style={{ fontWeight: 400 }}>
Because Uniswap V2 uses WETH under the hood, your Uniswap V1 WETH/ETH liquidity cannot be migrated. You

View File

@@ -58,7 +58,7 @@ function V1PairRemoval({
: new TokenAmount(token, ZERO)
const addTransaction = useTransactionAdder()
const isRemovalPending = useIsTransactionPending(pendingRemovalHash)
const isRemovalPending = useIsTransactionPending(pendingRemovalHash ?? undefined)
const remove = useCallback(() => {
if (!liquidityTokenAmount) return
@@ -79,11 +79,11 @@ function V1PairRemoval({
})
addTransaction(response, {
summary: `Remove ${token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH V1 liquidity`
summary: `Remove ${chainId && token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH V1 liquidity`
})
setPendingRemovalHash(response.hash)
})
.catch(error => {
.catch((error: Error) => {
console.error(error)
setConfirmingRemoval(false)
})
@@ -91,7 +91,7 @@ function V1PairRemoval({
const noLiquidityTokens = !!liquidityTokenAmount && liquidityTokenAmount.equalTo(ZERO)
const isSuccessfullyRemoved = !!pendingRemovalHash && !!noLiquidityTokens
const isSuccessfullyRemoved = !!pendingRemovalHash && noLiquidityTokens
return (
<AutoColumn gap="20px">
@@ -119,7 +119,7 @@ function V1PairRemoval({
</LightCard>
<TYPE.darkGray style={{ textAlign: 'center' }}>
{`Your Uniswap V1 ${
token.equals(WETH[chainId]) ? 'WETH' : token.symbol
chainId && token.equals(WETH[chainId]) ? 'WETH' : token.symbol
}/ETH liquidity will be redeemed for underlying assets.`}
</TYPE.darkGray>
</AutoColumn>
@@ -140,12 +140,12 @@ export default function RemoveV1Exchange({
const liquidityToken: Token | undefined = useMemo(
() =>
validatedAddress && token
validatedAddress && chainId && token
? new Token(chainId, validatedAddress, 18, `UNI-V1-${token.symbol}`, 'Uniswap V1')
: undefined,
[chainId, validatedAddress, token]
)
const userLiquidityBalance = useTokenBalance(account, liquidityToken)
const userLiquidityBalance = useTokenBalance(account ?? undefined, liquidityToken)
// redirect for invalid url params
if (!validatedAddress || tokenAddress === AddressZero) {
@@ -166,7 +166,7 @@ export default function RemoveV1Exchange({
{!account ? (
<TYPE.largeHeader>You must connect an account.</TYPE.largeHeader>
) : userLiquidityBalance && token ? (
) : userLiquidityBalance && token && exchangeContract ? (
<V1PairRemoval
exchangeContract={exchangeContract}
liquidityTokenAmount={userLiquidityBalance}

View File

@@ -29,7 +29,7 @@ export default function MigrateV1() {
// automatically add the search token
const token = useToken(tokenSearch)
const selectedTokenListTokens = useSelectedTokenList()
const isOnSelectedList = isTokenOnList(selectedTokenListTokens, token)
const isOnSelectedList = isTokenOnList(selectedTokenListTokens, token ?? undefined)
const allTokens = useAllTokens()
const addToken = useAddUserToken()
useEffect(() => {
@@ -41,27 +41,26 @@ export default function MigrateV1() {
// get V1 LP balances
const V1Exchanges = useAllTokenV1Exchanges()
const V1LiquidityTokens: Token[] = useMemo(() => {
return Object.keys(V1Exchanges).map(
exchangeAddress => new Token(chainId, exchangeAddress, 18, 'UNI-V1', 'Uniswap V1')
)
return chainId
? Object.keys(V1Exchanges).map(exchangeAddress => new Token(chainId, exchangeAddress, 18, 'UNI-V1', 'Uniswap V1'))
: []
}, [chainId, V1Exchanges])
const [V1LiquidityBalances, V1LiquidityBalancesLoading] = useTokenBalancesWithLoadingIndicator(
account,
account ?? undefined,
V1LiquidityTokens
)
const allV1PairsWithLiquidity = V1LiquidityTokens.filter(V1LiquidityToken => {
return (
V1LiquidityBalances?.[V1LiquidityToken.address] &&
JSBI.greaterThan(V1LiquidityBalances[V1LiquidityToken.address].raw, JSBI.BigInt(0))
)
const balance = V1LiquidityBalances?.[V1LiquidityToken.address]
return balance && JSBI.greaterThan(balance.raw, JSBI.BigInt(0))
}).map(V1LiquidityToken => {
return (
const balance = V1LiquidityBalances[V1LiquidityToken.address]
return balance ? (
<V1PositionCard
key={V1LiquidityToken.address}
token={V1Exchanges[V1LiquidityToken.address]}
V1LiquidityBalance={V1LiquidityBalances[V1LiquidityToken.address]}
V1LiquidityBalance={balance}
/>
)
) : null
})
// should never always be false, because a V1 exhchange exists for WETH on all testnets

View File

@@ -1,4 +0,0 @@
{
"extends": "../../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -1,4 +0,0 @@
{
"extends": "../../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -634,14 +634,7 @@ export default function RemoveLiquidity({
<RowBetween>
<div />
<div>
1 {currencyB?.symbol} ={' '}
{tokenB
? pair
.priceOf(tokenB)
.invert()
.toSignificant(6)
: '-'}{' '}
{currencyA?.symbol}
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</div>
</RowBetween>
</div>

View File

@@ -1,7 +0,0 @@
{
"extends": "../../../tsconfig.strict.json",
"include": [
"**/*",
"../../../node_modules/eslint-plugin-react/lib/types.d.ts"
]
}

View File

@@ -5,7 +5,7 @@ import ReactGA from 'react-ga'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
import AddressInputPanel from '../../components/AddressInputPanel'
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import { ButtonError, ButtonLight, ButtonPrimary, ButtonConfirmed } from '../../components/Button'
import Card, { GreyCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column'
import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal'
@@ -15,9 +15,10 @@ import { AutoRow, RowBetween } from '../../components/Row'
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
import BetterTradeLink from '../../components/swap/BetterTradeLink'
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
import { ArrowWrapper, BottomGrouping, Dots, SwapCallbackError, Wrapper } from '../../components/swap/styleds'
import { ArrowWrapper, BottomGrouping, SwapCallbackError, Wrapper } from '../../components/swap/styleds'
import TradePrice from '../../components/swap/TradePrice'
import TokenWarningModal from '../../components/TokenWarningModal'
import ProgressSteps from '../../components/ProgressSteps'
import { BETTER_TRADE_LINK_THRESHOLD, INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
import { getTradeVersion, isTradeBetter } from '../../data/V1'
@@ -42,6 +43,7 @@ import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
import AppBody from '../AppBody'
import { ClickableText } from '../Pool/styleds'
import Loader from '../../components/Loader'
export default function Swap() {
const loadedUrlParams = useDefaultsFromURLSearch()
@@ -294,7 +296,7 @@ export default function Swap() {
<AutoColumn gap={'md'}>
<CurrencyInputPanel
label={independentField === Field.OUTPUT && !showWrap ? 'From (estimated)' : 'From'}
label={independentField === Field.OUTPUT && !showWrap && trade ? 'From (estimated)' : 'From'}
value={formattedAmounts[Field.INPUT]}
showMaxButton={!atMaxAmountInput}
currency={currencies[Field.INPUT]}
@@ -304,9 +306,8 @@ export default function Swap() {
otherCurrency={currencies[Field.OUTPUT]}
id="swap-currency-input"
/>
<AutoColumn justify="space-between">
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<AutoRow justify={isExpertMode ? 'space-between' : 'center'} style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable>
<ArrowDown
size="16"
@@ -327,7 +328,7 @@ export default function Swap() {
<CurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={independentField === Field.INPUT && !showWrap ? 'To (estimated)' : 'To'}
label={independentField === Field.INPUT && !showWrap && trade ? 'To (estimated)' : 'To'}
showMaxButton={false}
currency={currencies[Field.OUTPUT]}
onCurrencySelect={handleOutputSelect}
@@ -352,19 +353,18 @@ export default function Swap() {
{showWrap ? null : (
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
<AutoColumn gap="4px">
<RowBetween align="center">
<Text fontWeight={500} fontSize={14} color={theme.text2}>
Price
</Text>
<TradePrice
inputCurrency={currencies[Field.INPUT]}
outputCurrency={currencies[Field.OUTPUT]}
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
{Boolean(trade) && (
<RowBetween align="center">
<Text fontWeight={500} fontSize={14} color={theme.text2}>
Price
</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
@@ -393,20 +393,23 @@ export default function Swap() {
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<ButtonPrimary
<ButtonConfirmed
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
width="48%"
altDisbaledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
altDisabledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
confirmed={approval === ApprovalState.APPROVED}
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
'Approve ' + currencies[Field.INPUT]?.symbol
)}
</ButtonPrimary>
</ButtonConfirmed>
<ButtonError
onClick={() => {
if (isExpertMode) {
@@ -463,6 +466,7 @@ export default function Swap() {
</Text>
</ButtonError>
)}
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkVersion && <BetterTradeLink version={betterTradeLinkVersion} />}
</BottomGrouping>

View File

@@ -1,4 +0,0 @@
{
"extends": "../../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -1 +1,26 @@
/// <reference types="react-scripts" />
declare module 'jazzicon' {
export default function(diameter: number, seed: number): HTMLElement
}
declare module 'fortmatic'
interface Window {
ethereum?: {
isMetaMask?: true
on?: (...args: any[]) => void
removeListener?: (...args: any[]) => void
}
web3?: {}
}
declare module 'content-hash' {
declare function decode(x: string): string
declare function getCodec(x: string): string
}
declare module 'multihashes' {
declare function decode(buff: Uint8Array): { code: number; name: string; length: number; digest: Uint8Array }
declare function toB58String(hash: Uint8Array): string
}

View File

@@ -18,8 +18,10 @@ export type PopupContent =
}
}
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('updateBlockNumber')
export const toggleWalletModal = createAction<void>('toggleWalletModal')
export const toggleSettingsMenu = createAction<void>('toggleSettingsMenu')
export const addPopup = createAction<{ key?: string; removeAfterMs?: number | null; content: PopupContent }>('addPopup')
export const removePopup = createAction<{ key: string }>('removePopup')
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('app/updateBlockNumber')
export const toggleWalletModal = createAction<void>('app/toggleWalletModal')
export const toggleSettingsMenu = createAction<void>('app/toggleSettingsMenu')
export const addPopup = createAction<{ key?: string; removeAfterMs?: number | null; content: PopupContent }>(
'app/addPopup'
)
export const removePopup = createAction<{ key: string }>('app/removePopup')

View File

@@ -5,7 +5,7 @@ import useIsWindowVisible from '../../hooks/useIsWindowVisible'
import { updateBlockNumber } from './actions'
import { useDispatch } from 'react-redux'
export default function Updater() {
export default function Updater(): null {
const { library, chainId } = useActiveWeb3React()
const dispatch = useDispatch()
@@ -31,7 +31,7 @@ export default function Updater() {
// attach/detach listeners
useEffect(() => {
if (!library || !chainId || !windowVisible) return
if (!library || !chainId || !windowVisible) return undefined
setState({ chainId, blockNumber: null })

View File

@@ -7,4 +7,4 @@ export enum Field {
CURRENCY_B = 'CURRENCY_B'
}
export const typeInput = createAction<{ field: Field; typedValue: string }>('typeInputBurn')
export const typeInput = createAction<{ field: Field; typedValue: string }>('burn/typeInputBurn')

View File

@@ -0,0 +1,5 @@
import { createAction } from '@reduxjs/toolkit'
// fired once when the app reloads but before the app renders
// allows any updates to be applied to store data loaded from localStorage
export const updateVersion = createAction<void>('global/updateVersion')

View File

@@ -2,6 +2,7 @@ import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import { save, load } from 'redux-localstorage-simple'
import application from './application/reducer'
import { updateVersion } from './global/actions'
import user from './user/reducer'
import transactions from './transactions/reducer'
import swap from './swap/reducer'
@@ -10,8 +11,6 @@ import lists from './lists/reducer'
import burn from './burn/reducer'
import multicall from './multicall/reducer'
import { updateVersion } from './user/actions'
const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists']
const store = configureStore({

View File

@@ -1,6 +1,6 @@
import { createStore, Store } from 'redux'
import { DEFAULT_LIST_OF_LISTS, DEFAULT_TOKEN_LIST_URL } from '../../constants/lists'
import { updateVersion } from '../user/actions'
import { updateVersion } from '../global/actions'
import { fetchTokenList, acceptListUpdate, addList, removeList, selectList } from './actions'
import reducer, { ListsState } from './reducer'
import UNISWAP_DEFAULT_TOKEN_LIST from '@uniswap/default-token-list'

View File

@@ -2,7 +2,7 @@ import { createReducer } from '@reduxjs/toolkit'
import { getVersionUpgrade, VersionUpgrade } from '@uniswap/token-lists'
import { TokenList } from '@uniswap/token-lists/dist/types'
import { DEFAULT_LIST_OF_LISTS, DEFAULT_TOKEN_LIST_URL } from '../../constants/lists'
import { updateVersion } from '../user/actions'
import { updateVersion } from '../global/actions'
import { acceptListUpdate, addList, fetchTokenList, removeList, selectList } from './actions'
import UNISWAP_DEFAULT_LIST from '@uniswap/default-token-list'

View File

@@ -5,5 +5,5 @@ export enum Field {
CURRENCY_B = 'CURRENCY_B'
}
export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('typeInputMint')
export const resetMintState = createAction<void>('resetMintState')
export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('mint/typeInputMint')
export const resetMintState = createAction<void>('mint/resetMintState')

View File

@@ -72,7 +72,7 @@ export function useDerivedMintInfo(
if (otherTypedValue && currencies[dependentField]) {
return tryParseAmount(otherTypedValue, currencies[dependentField])
}
return
return undefined
} else if (independentAmount) {
// we wrap the currencies just to get the price in terms of the other token
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
@@ -85,9 +85,9 @@ export function useDerivedMintInfo(
: pair.priceOf(tokenB).quote(wrappedIndependentAmount)
return dependentCurrency === ETHER ? CurrencyAmount.ether(dependentTokenAmount.raw) : dependentTokenAmount
}
return
return undefined
} else {
return
return undefined
}
}, [noLiquidity, otherTypedValue, currencies, dependentField, independentAmount, currencyA, chainId, currencyB, pair])
const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = {
@@ -95,18 +95,18 @@ export function useDerivedMintInfo(
[Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount
}
const token0Price = pair?.token0Price
const price = useMemo(() => {
if (noLiquidity) {
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
if (currencyAAmount && currencyBAmount) {
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw)
}
return
return undefined
} else {
return token0Price
const wrappedCurrencyA = wrappedCurrency(currencyA, chainId)
return pair && wrappedCurrencyA ? pair.priceOf(wrappedCurrencyA) : undefined
}
}, [noLiquidity, token0Price, parsedAmounts])
}, [chainId, currencyA, noLiquidity, pair, parsedAmounts])
// liquidity minted
const liquidityMinted = useMemo(() => {
@@ -118,7 +118,7 @@ export function useDerivedMintInfo(
if (pair && totalSupply && tokenAmountA && tokenAmountB) {
return pair.getLiquidityMinted(totalSupply, tokenAmountA, tokenAmountB)
} else {
return
return undefined
}
}, [parsedAmounts, chainId, pair, totalSupply])
@@ -126,7 +126,7 @@ export function useDerivedMintInfo(
if (liquidityMinted && totalSupply) {
return new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
} else {
return
return undefined
}
}, [liquidityMinted, totalSupply])

View File

@@ -34,23 +34,23 @@ export interface ListenerOptions {
}
export const addMulticallListeners = createAction<{ chainId: number; calls: Call[]; options?: ListenerOptions }>(
'addMulticallListeners'
'multicall/addMulticallListeners'
)
export const removeMulticallListeners = createAction<{ chainId: number; calls: Call[]; options?: ListenerOptions }>(
'removeMulticallListeners'
'multicall/removeMulticallListeners'
)
export const fetchingMulticallResults = createAction<{ chainId: number; calls: Call[]; fetchingBlockNumber: number }>(
'fetchingMulticallResults'
'multicall/fetchingMulticallResults'
)
export const errorFetchingMulticallResults = createAction<{
chainId: number
calls: Call[]
fetchingBlockNumber: number
}>('errorFetchingMulticallResults')
}>('multicall/errorFetchingMulticallResults')
export const updateMulticallResults = createAction<{
chainId: number
blockNumber: number
results: {
[callKey: string]: string | null
}
}>('updateMulticallResults')
}>('multicall/updateMulticallResults')

View File

@@ -30,7 +30,8 @@ function isMethodArg(x: unknown): x is MethodArg {
function isValidMethodArgs(x: unknown): x is MethodArgs | undefined {
return (
x === undefined || (Array.isArray(x) && x.every(y => isMethodArg(y) || (Array.isArray(y) && y.every(isMethodArg))))
x === undefined ||
(Array.isArray(x) && x.every(xi => isMethodArg(xi) || (Array.isArray(xi) && xi.every(isMethodArg))))
)
}
@@ -67,7 +68,7 @@ function useCallsData(calls: (Call | undefined)[], options?: ListenerOptions): C
// update listeners when there is an actual change that persists for at least 100ms
useEffect(() => {
const callKeys: string[] = JSON.parse(serializedCallKeys)
if (!chainId || callKeys.length === 0) return
if (!chainId || callKeys.length === 0) return undefined
const calls = callKeys.map(key => parseCallKey(key))
dispatch(
addMulticallListeners({

View File

@@ -110,7 +110,7 @@ export function outdatedListeningKeys(
})
}
export default function Updater() {
export default function Updater(): null {
const dispatch = useDispatch<AppDispatch>()
const state = useSelector<AppState, AppState['multicall']>(state => state.multicall)
// wait for listeners to settle before triggering updates

View File

@@ -5,14 +5,14 @@ export enum Field {
OUTPUT = 'OUTPUT'
}
export const selectCurrency = createAction<{ field: Field; currencyId: string }>('selectCurrency')
export const switchCurrencies = createAction<void>('switchCurrencies')
export const typeInput = createAction<{ field: Field; typedValue: string }>('typeInput')
export const selectCurrency = createAction<{ field: Field; currencyId: string }>('swap/selectCurrency')
export const switchCurrencies = createAction<void>('swap/switchCurrencies')
export const typeInput = createAction<{ field: Field; typedValue: string }>('swap/typeInput')
export const replaceSwapState = createAction<{
field: Field
typedValue: string
inputCurrencyId?: string
outputCurrencyId?: string
recipient: string | null
}>('replaceSwapState')
export const setRecipient = createAction<{ recipient: string | null }>('setRecipient')
}>('swap/replaceSwapState')
export const setRecipient = createAction<{ recipient: string | null }>('swap/setRecipient')

View File

@@ -71,7 +71,7 @@ export function useSwapActionHandlers(): {
// try to parse a user entered amount for a given token
export function tryParseAmount(value?: string, currency?: Currency): CurrencyAmount | undefined {
if (!value || !currency) {
return
return undefined
}
try {
const typedValueParsed = parseUnits(value, currency.decimals).toString()
@@ -85,7 +85,7 @@ export function tryParseAmount(value?: string, currency?: Currency): CurrencyAmo
console.debug(`Failed to parse input amount: "${value}"`, error)
}
// necessary for all paths to return a value
return
return undefined
}
const BAD_RECIPIENT_ADDRESSES: string[] = [

View File

@@ -50,6 +50,14 @@ export function useIsTransactionPending(transactionHash?: string): boolean {
return !transactions[transactionHash].receipt
}
/**
* Returns whether a transaction happened in the last day (86400 seconds * 1000 milliseconds / second)
* @param tx to check for recency
*/
export function isTransactionRecent(tx: TransactionDetails): boolean {
return new Date().getTime() - tx.addedTime < 86_400_000
}
// returns whether a token has a pending approval transaction
export function useHasPendingApproval(tokenAddress: string | undefined, spender: string | undefined): boolean {
const allTransactions = useAllTransactions()
@@ -58,13 +66,14 @@ export function useHasPendingApproval(tokenAddress: string | undefined, spender:
typeof tokenAddress === 'string' &&
typeof spender === 'string' &&
Object.keys(allTransactions).some(hash => {
if (allTransactions[hash]?.receipt) {
const tx = allTransactions[hash]
if (!tx) return false
if (tx.receipt) {
return false
} else {
return (
allTransactions[hash]?.approval?.tokenAddress === tokenAddress &&
allTransactions[hash]?.approval?.spender === spender
)
const approval = tx.approval
if (!approval) return false
return approval.spender === spender && approval.tokenAddress === tokenAddress && isTransactionRecent(tx)
}
}),
[allTransactions, spender, tokenAddress]

View File

@@ -26,7 +26,7 @@ export function shouldCheck(
}
}
export default function Updater() {
export default function Updater(): null {
const { chainId, library } = useActiveWeb3React()
const lastBlockNumber = useBlockNumber()

View File

@@ -1,4 +0,0 @@
{
"extends": "../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -13,17 +13,16 @@ export interface SerializedPair {
token1: SerializedToken
}
export const updateVersion = createAction<void>('updateVersion')
export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('updateMatchesDarkMode')
export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('updateUserDarkMode')
export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('updateUserExpertMode')
export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('user/updateMatchesDarkMode')
export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode')
export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode')
export const updateUserSlippageTolerance = createAction<{ userSlippageTolerance: number }>(
'updateUserSlippageTolerance'
'user/updateUserSlippageTolerance'
)
export const updateUserDeadline = createAction<{ userDeadline: number }>('updateUserDeadline')
export const addSerializedToken = createAction<{ serializedToken: SerializedToken }>('addSerializedToken')
export const removeSerializedToken = createAction<{ chainId: number; address: string }>('removeSerializedToken')
export const addSerializedPair = createAction<{ serializedPair: SerializedPair }>('addSerializedPair')
export const updateUserDeadline = createAction<{ userDeadline: number }>('user/updateUserDeadline')
export const addSerializedToken = createAction<{ serializedToken: SerializedToken }>('user/addSerializedToken')
export const removeSerializedToken = createAction<{ chainId: number; address: string }>('user/removeSerializedToken')
export const addSerializedPair = createAction<{ serializedPair: SerializedPair }>('user/addSerializedPair')
export const removeSerializedPair = createAction<{ chainId: number; tokenAAddress: string; tokenBAddress: string }>(
'removeSerializedPair'
'user/removeSerializedPair'
)

View File

@@ -1,6 +1,6 @@
import { createStore, Store } from 'redux'
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
import { updateVersion } from './actions'
import { updateVersion } from '../global/actions'
import reducer, { initialState, UserState } from './reducer'
describe('swap reducer', () => {

View File

@@ -1,5 +1,6 @@
import { INITIAL_ALLOWED_SLIPPAGE, DEFAULT_DEADLINE_FROM_NOW } from '../../constants'
import { createReducer } from '@reduxjs/toolkit'
import { updateVersion } from '../global/actions'
import {
addSerializedPair,
addSerializedToken,
@@ -9,7 +10,6 @@ import {
SerializedToken,
updateMatchesDarkMode,
updateUserDarkMode,
updateVersion,
updateUserExpertMode,
updateUserSlippageTolerance,
updateUserDeadline

View File

@@ -3,7 +3,7 @@ import { useDispatch } from 'react-redux'
import { AppDispatch } from '../index'
import { updateMatchesDarkMode } from './actions'
export default function Updater() {
export default function Updater(): null {
const dispatch = useDispatch<AppDispatch>()
// keep dark mode in sync with the system

View File

@@ -90,7 +90,7 @@ export function useTokenBalances(
// get the balance for a single token/account combo
export function useTokenBalance(account?: string, token?: Token): TokenAmount | undefined {
const tokenBalances = useTokenBalances(account, [token])
if (!token) return
if (!token) return undefined
return tokenBalances[token.address]
}
@@ -109,10 +109,10 @@ export function useCurrencyBalances(
return useMemo(
() =>
currencies?.map(currency => {
if (!account || !currency) return
if (!account || !currency) return undefined
if (currency instanceof Token) return tokenBalances[currency.address]
if (currency === ETHER) return ethBalance[account]
return
return undefined
}) ?? [],
[account, currencies, ethBalance, tokenBalances]
)

View File

@@ -5,7 +5,7 @@ import { parse } from 'qs'
import { AppDispatch } from '../state'
import { updateUserDarkMode } from '../state/user/actions'
export default function DarkModeQueryParamReader({ location: { search } }: RouteComponentProps) {
export default function DarkModeQueryParamReader({ location: { search } }: RouteComponentProps): null {
const dispatch = useDispatch<AppDispatch>()
useEffect(() => {

View File

@@ -1,4 +0,0 @@
{
"extends": "../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -1,4 +0,0 @@
declare module 'content-hash' {
declare function decode(x: string): string
declare function getCodec(x: string): string
}

View File

@@ -6,7 +6,7 @@ import { MIN_ETH } from '../constants'
* @param currencyAmount to return max of
*/
export function maxAmountSpend(currencyAmount?: CurrencyAmount): CurrencyAmount | undefined {
if (!currencyAmount) return
if (!currencyAmount) return undefined
if (currencyAmount.currency === ETHER) {
if (JSBI.greaterThan(currencyAmount.raw, MIN_ETH)) {
return CurrencyAmount.ether(JSBI.subtract(currencyAmount.raw, MIN_ETH))

View File

@@ -1,4 +0,0 @@
declare module 'multihashes' {
declare function decode(buff: Uint8Array): { code: number; name: string; length: number; digest: Uint8Array }
declare function toB58String(hash: Uint8Array): string
}

View File

@@ -2,6 +2,6 @@ const ENS_NAME_REGEX = /^(([a-zA-Z0-9]+\.)+)eth(\/.*)?$/
export function parseENSAddress(ensAddress: string): { ensName: string; ensPath: string | undefined } | undefined {
const match = ensAddress.match(ENS_NAME_REGEX)
if (!match) return
if (!match) return undefined
return { ensName: `${match[1].toLowerCase()}eth`, ensPath: match[3] }
}

View File

@@ -1,4 +0,0 @@
{
"extends": "../../tsconfig.strict.json",
"include": ["**/*"]
}

View File

@@ -27,7 +27,7 @@ export default function useUSDCPrice(currency?: Currency): Price | undefined {
return useMemo(() => {
if (!currency || !wrapped || !chainId) {
return
return undefined
}
// handle weth/eth
if (wrapped.equals(WETH[chainId])) {
@@ -61,6 +61,6 @@ export default function useUSDCPrice(currency?: Currency): Price | undefined {
return new Price(currency, USDC, usdcPrice.denominator, usdcPrice.numerator)
}
}
return
return undefined
}, [chainId, currency, ethPair, ethPairState, usdcEthPair, usdcEthPairState, usdcPair, usdcPairState, wrapped])
}

View File

@@ -8,11 +8,18 @@
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"strict": true,
"alwaysStrict": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
@@ -29,8 +36,7 @@
"cypress"
],
"include": [
"**/*.js",
"**/*.ts",
"**/*.tsx"
"./src/**/*.ts",
"./src/**/*.tsx"
]
}

Some files were not shown because too many files have changed in this diff Show More