Compare commits

...

9 Commits

Author SHA1 Message Date
Moody Salem
b2f88965a9 handle errors better 2021-05-12 22:05:00 -05:00
Moody Salem
95db44e0fc add a little state to the automatic issue body 2021-05-12 18:20:16 -05:00
Moody Salem
7d45ff5ca8 fix 0 decimal tokens error 2021-05-12 17:29:02 -05:00
Moody Salem
8964cf86aa nit with how we convert percent to negative value 2021-05-12 17:13:40 -05:00
Moody Salem
0e9f23ed56 hover text nit 2021-05-12 17:12:16 -05:00
Moody Salem
e08e597655 show list in import token dialog 2021-05-12 17:01:20 -05:00
Moody Salem
744db49803 do not show duplicate token results, and stop searching as soon as possible 2021-05-12 16:54:05 -05:00
Moody Salem
54f59e02fd add page url to the issue template 2021-05-12 16:17:46 -05:00
Moody Salem
7950e5c083 fixes https://github.com/Uniswap/uniswap-interface/issues/1548 2021-05-12 16:16:00 -05:00
13 changed files with 109 additions and 76 deletions

View File

@@ -1,4 +1,4 @@
blank_issues_enabled: false
blank_issues_enabled: true
contact_links:
- name: Support
url: https://discord.gg/FCfyBSbCU5

View File

@@ -22,12 +22,6 @@ To access the Uniswap Interface, use an IPFS gateway link from the
[latest release](https://github.com/Uniswap/uniswap-interface/releases/latest),
or visit [app.uniswap.org](https://app.uniswap.org).
## Listing a token
Please see the
[@uniswap/default-token-list](https://github.com/uniswap/default-token-list)
repository.
## Development
### Install Dependencies

View File

@@ -51,7 +51,7 @@
"@uniswap/v2-sdk": "^3.0.0-alpha.0",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "1.0.0",
"@uniswap/v3-sdk": "^3.0.0-alpha.0",
"@uniswap/v3-sdk": "^3.0.0-alpha.1",
"@web3-react/core": "^6.0.9",
"@web3-react/fortmatic-connector": "^6.0.9",
"@web3-react/injected-connector": "^6.0.7",

View File

@@ -25,7 +25,7 @@ export function FiatValue({
return (
<TYPE.body fontSize={14} color={fiatValue ? theme.text2 : theme.text4}>
{fiatValue ? '~' : ''}$
<HoverInlineText text={fiatValue ? Number(fiatValue?.toSignificant(6)).toLocaleString('en') : '-'} />{' '}
<HoverInlineText text={fiatValue ? fiatValue?.toSignificant(6, { groupSeparator: ',' }) : '-'} />{' '}
{priceImpact ? (
<span style={{ color: priceImpactColor }}> ({priceImpact.multiply(-1).toSignificant(3)}%)</span>
) : null}

View File

@@ -1,4 +1,5 @@
import React, { ErrorInfo } from 'react'
import store, { AppState } from '../../state'
import { ExternalLink, ThemedBackground, TYPE } from '../../theme'
import { AutoColumn } from '../Column'
import styled from 'styled-components'
@@ -114,22 +115,45 @@ export default class ErrorBoundary extends React.Component<unknown, ErrorBoundar
}
}
function getRelevantState(): null | keyof AppState {
const path = window.location.hash
if (!path.startsWith('#/')) {
return null
}
const pieces = path.substring(2).split(/[\/\\?]/)
switch (pieces[0]) {
case 'swap':
return 'swap'
case 'add':
if (pieces[1] === 'v2') return 'mint'
else return 'mintV3'
case 'remove':
if (pieces[1] === 'v2') return 'burn'
else return 'burnV3'
}
return null
}
function issueBody(error: Error): string {
if (!error) throw new Error('no error to report')
const relevantState = getRelevantState()
const deviceData = getUserAgent()
return `**Bug Description**
return `## URL
App crashed
${window.location.href}
**Steps to Reproduce**
1. Go to ...
2. Click on ...
...
${
relevantState
? `## \`${relevantState}\` state
\`\`\`json
${JSON.stringify(store.getState()[relevantState], null, 2)}
\`\`\`
`
: ''
}
${
error.name &&
`**Error**
`## Error
\`\`\`
${error.name}${error.message && `: ${error.message}`}
@@ -138,7 +162,7 @@ ${error.name}${error.message && `: ${error.message}`}
}
${
error.stack &&
`**Stacktrace**
`## Stacktrace
\`\`\`
${error.stack}
@@ -147,9 +171,9 @@ ${error.stack}
}
${
deviceData &&
`**Device data**
`## Device data
\`\`\`json5
\`\`\`json
${JSON.stringify(deviceData, null, 2)}
\`\`\`
`

View File

@@ -2,8 +2,8 @@ import Tooltip from 'components/Tooltip'
import React, { useState } from 'react'
import styled from 'styled-components'
const TextWrapper = styled.span<{ margin: boolean; link: boolean; fontSize?: string; adjustSize?: boolean }>`
position: relative;
const TextWrapper = styled.span<{ margin: boolean; link?: boolean; fontSize?: string; adjustSize?: boolean }>`
cursor: auto;
margin-left: ${({ margin }) => margin && '4px'};
color: ${({ theme, link }) => (link ? theme.blue1 : theme.text1)};
font-size: ${({ fontSize }) => fontSize ?? 'inherit'};
@@ -32,7 +32,7 @@ const HoverInlineText = ({
const [showHover, setShowHover] = useState(false)
if (!text) {
return <span></span>
return <span />
}
if (text.length > maxCharacters) {
@@ -43,7 +43,7 @@ const HoverInlineText = ({
onMouseLeave={() => setShowHover(false)}
margin={margin}
adjustSize={adjustSize}
link={!!link}
link={link}
fontSize={fontSize}
{...rest}
>
@@ -54,7 +54,7 @@ const HoverInlineText = ({
}
return (
<TextWrapper margin={margin} adjustSize={adjustSize} link={!!link} fontSize={fontSize} {...rest}>
<TextWrapper margin={margin} adjustSize={adjustSize} link={link} fontSize={fontSize} {...rest}>
{text}
</TextWrapper>
)

View File

@@ -1,6 +1,7 @@
import { Currency, Token } from '@uniswap/sdk-core'
import React, { useCallback, useEffect, useState } from 'react'
import useLast from '../../hooks/useLast'
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
import Modal from '../Modal'
import { CurrencySearch } from './CurrencySearch'
import { ImportToken } from './ImportToken'
@@ -81,6 +82,7 @@ export default function CurrencySearchModal({
<ImportToken
tokens={[importToken]}
onDismiss={onDismiss}
list={importToken instanceof WrappedTokenInfo ? importToken.list : undefined}
onBack={() =>
setModalView(prevView && prevView !== CurrencyModalView.importToken ? prevView : CurrencyModalView.search)
}

View File

@@ -3,13 +3,18 @@ import { useMemo } from 'react'
import { isAddress } from '../../utils'
import { Token } from '@uniswap/sdk-core'
export function filterTokens<T extends Token | TokenInfo>(tokens: T[], search: string): T[] {
if (search.length === 0) return tokens
const alwaysTrue = () => true
/**
* Create a filter function to apply to a token for whether it matches a particular search query
* @param search the search query to apply to the token
*/
export function createTokenFilterFunction<T extends Token | TokenInfo>(search: string): (tokens: T) => boolean {
const searchingAddress = isAddress(search)
if (searchingAddress) {
return tokens.filter((token) => token.address === searchingAddress)
const lower = searchingAddress.toLowerCase()
return (t: T) => ('isToken' in t ? searchingAddress === t.address : lower === t.address.toLowerCase())
}
const lowerSearchParts = search
@@ -17,9 +22,7 @@ export function filterTokens<T extends Token | TokenInfo>(tokens: T[], search: s
.split(/\s+/)
.filter((s) => s.length > 0)
if (lowerSearchParts.length === 0) {
return tokens
}
if (lowerSearchParts.length === 0) return alwaysTrue
const matchesSearch = (s: string): boolean => {
const sParts = s
@@ -30,10 +33,11 @@ export function filterTokens<T extends Token | TokenInfo>(tokens: T[], search: s
return lowerSearchParts.every((p) => p.length === 0 || sParts.some((sp) => sp.startsWith(p) || sp.endsWith(p)))
}
return tokens.filter((token) => {
const { symbol, name } = token
return (symbol && matchesSearch(symbol)) || (name && matchesSearch(name))
})
return ({ name, symbol }: T): boolean => Boolean((symbol && matchesSearch(symbol)) || (name && matchesSearch(name)))
}
export function filterTokens<T extends Token | TokenInfo>(tokens: T[], search: string): T[] {
return tokens.filter(createTokenFilterFunction(search))
}
export function useSortedTokensByQuery(tokens: Token[] | undefined, searchQuery: string): Token[] {

View File

@@ -9,7 +9,7 @@ import { ErrorText, ErrorPill } from './styleds'
export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) {
return (
<ErrorText fontWeight={500} fontSize={12} severity={warningSeverity(priceImpact)}>
{priceImpact ? `-${priceImpact.toFixed(2)}%` : '-'}
{priceImpact ? `${priceImpact.multiply(-1).toFixed(2)}%` : '-'}
</ErrorText>
)
}
@@ -17,7 +17,7 @@ export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Pe
export function SmallFormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) {
return (
<ErrorPill fontWeight={500} fontSize={12} severity={warningSeverity(priceImpact)}>
{priceImpact ? `(-${priceImpact.toFixed(2)}%)` : '-'}
{priceImpact ? `(${priceImpact.multiply(-1).toFixed(2)}%)` : '-'}
</ErrorPill>
)
}

View File

@@ -2,7 +2,7 @@ import { parseBytes32String } from '@ethersproject/strings'
import { Currency, currencyEquals, ETHER, Token } from '@uniswap/sdk-core'
import { arrayify } from 'ethers/lib/utils'
import { useMemo } from 'react'
import { filterTokens } from '../components/SearchModal/filtering'
import { createTokenFilterFunction } from '../components/SearchModal/filtering'
import { useAllLists, useCombinedActiveList, useInactiveListUrls } from '../state/lists/hooks'
import { WrappedTokenInfo } from '../state/lists/wrappedTokenInfo'
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
@@ -61,21 +61,28 @@ export function useSearchInactiveTokenLists(search: string | undefined, minResul
const lists = useAllLists()
const inactiveUrls = useInactiveListUrls()
const { chainId } = useActiveWeb3React()
const activeTokens = useAllTokens()
return useMemo(() => {
if (!search || search.trim().length === 0) return []
let result: WrappedTokenInfo[] = []
const tokenFilter = createTokenFilterFunction(search)
const result: WrappedTokenInfo[] = []
const addressSet: { [address: string]: true } = {}
for (const url of inactiveUrls) {
const list = lists[url].current
if (!list) continue
const matching = filterTokens(
list.tokens.filter((token) => token.chainId === chainId),
search
)
result = [...result, ...matching.map((tokenInfo) => new WrappedTokenInfo(tokenInfo, list))]
if (result.length >= minResults) return result
for (const tokenInfo of list.tokens) {
if (tokenInfo.chainId === chainId && tokenFilter(tokenInfo)) {
const wrapped = new WrappedTokenInfo(tokenInfo, list)
if (!(wrapped.address in activeTokens) && !addressSet[wrapped.address]) {
addressSet[wrapped.address] = true
result.push(wrapped)
if (result.length >= minResults) return result
}
}
}
}
return result
}, [chainId, inactiveUrls, lists, minResults, search])
}, [activeTokens, chainId, inactiveUrls, lists, minResults, search])
}
export function useIsTokenActive(token: Token | undefined | null): boolean {

View File

@@ -133,6 +133,26 @@ function useSwapCallArguments(
}, [account, allowedSlippage, chainId, deadline, library, recipient, routerContract, signatureData, trade])
}
export function swapErrorToUserReadableMessage(error: { reason: string }): string {
switch (error.reason) {
case 'UniswapV2Router: EXPIRED':
return 'The transaction could not be sent because the deadline has passed. Please check that your transaction deadline is not too low.'
case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT':
case 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT':
return 'This transaction will not succeed either due to price movement or fee on transfer. Try increasing your slippage tolerance.'
case 'UniswapV2: TRANSFER_FAILED':
return 'The token could not be transferred. There may be an issue with the token.'
case 'UniswapV2: K':
return 'The Uniswap invariant x*y=k was not satisfied by the swap. This usually means one of the tokens you are swapping incorporates custom behavior on transfer.'
case 'Too little received':
case 'Too much requested':
case 'STF':
return 'This transaction will not succeed due to price movement. Try increasing your slippage tolerance.'
default:
return 'Unknown error. Please join the Discord to get help.'
}
}
// returns a function that will execute a swap, if the parameters are all valid
// and the user has approved the slippage adjusted input amount for the trade
export function useSwapCallback(
@@ -198,28 +218,7 @@ export function useSwapCallback(
})
.catch((callError) => {
console.debug('Call threw error', call, callError)
let errorMessage: string
switch (callError.reason) {
case 'UniswapV2Router: EXPIRED':
errorMessage =
'The transaction could not be sent because the deadline has passed. Please check that your transaction deadline is not too low.'
break
case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT':
case 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT':
errorMessage =
'This transaction will not succeed either due to price movement or fee on transfer. Try increasing your slippage tolerance.'
break
case 'UniswapV2: TRANSFER_FAILED':
errorMessage = 'The token could not be transferred. There may be an issue with the token.'
break
case 'UniswapV2: K':
errorMessage =
'The Uniswap invariant x*y=k was not satisfied by the swap. This usually means one of the tokens you are swapping incorporates custom behavior on transfer.'
break
default:
return { call }
}
return { call, error: new Error(errorMessage) }
return { call, error: new Error(swapErrorToUserReadableMessage(callError)) }
})
})
})
@@ -289,7 +288,10 @@ export function useSwapCallback(
} else {
// otherwise, the error was unexpected and we need to convey that
console.error(`Swap failed`, error, address, calldata, value)
throw new Error(`Swap failed: ${error.message}`)
throw new Error(
`Swap failed: ${'reason' in error ? swapErrorToUserReadableMessage(error) : error.message}`
)
}
})
},

View File

@@ -14,7 +14,7 @@ export function formatTokenAmount(amount: CurrencyAmount<Currency> | undefined,
return '<0.00001'
}
return amount.toSignificant(Math.min(sigFigs, amount.currency.decimals))
return amount.toSignificant(sigFigs)
}
export function formatPrice(price: Price<Currency, Currency> | undefined, sigFigs: number) {

View File

@@ -4179,10 +4179,10 @@
"@uniswap/v3-core" "1.0.0"
base64-sol "1.0.1"
"@uniswap/v3-sdk@^3.0.0-alpha.0":
version "3.0.0-alpha.0"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.0.0-alpha.0.tgz#155067312f07d0f09f69df4c64726287fe762c48"
integrity sha512-wlXX7otryzTOzTPx9u2p4U1ZXOVanL/I523s2AHLkxGVDlzwl0SiCtj7qLj65cet8u0eG9hlLw1N8GNQx4EMrQ==
"@uniswap/v3-sdk@^3.0.0-alpha.1":
version "3.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.0.0-alpha.1.tgz#33fcd4b1587d323c6afde3bb546ad0bb1c77e492"
integrity sha512-3+rVWwGlryEX/Nu7qevBrScTjZ4791fSfmLDz+U5ofWQL/edhZkjgTY1I/fkndUSI8FKWRppRAdzqcnmbpqqlQ==
dependencies:
"@ethersproject/abi" "^5.0.12"
"@ethersproject/solidity" "^5.0.9"