Compare commits

..

38 Commits

Author SHA1 Message Date
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
Ian Lapham
dd33205bf6 update string in token amount formatting (#1539) 2021-05-12 14:19:01 -04:00
Moody Salem
397a20b9ec fix the issue title 2021-05-12 13:14:02 -05:00
Moody Salem
0aac0b43aa clean up the error boundary 2021-05-12 13:11:10 -05:00
Ian Lapham
702500794d truncate and format large numbers (#1518)
* truncate and format large numbers

* update truncation shorthand

* update logic for detecting small amount
2021-05-12 14:08:05 -04:00
Moody Salem
8bea95fab2 fix the transaction deadline errors 2021-05-12 13:01:31 -05:00
Moody Salem
27094c87f2 clean up price impact formatting 2021-05-12 12:39:21 -05:00
Justin Domingue
bede9171c3 add header background on scroll body overlap (#1531)
* add header background-color on scroll overlap

* slide background in/out on scroll

* remove unused import

Co-authored-by: Justin Domingue <domingue.justin@gmail.com>
2021-05-12 13:19:43 -04:00
Justin Domingue
251d7c0bc2 Fix NFT SVG performance issue in browser (#1509)
* only animate NFT SVG on hover by using a canvas

* handle high dpis

* animation transition between canvas and img

* set start state to not animated

* removed animations that were causing issues on Firefox

* simplify code

* remove debugger statement

* remove useEffect in favor of an event handler

* hide canvas without unmounting to avoid blinking

* fix lint error

* fix flicker on hover by leaving canvas always visible

* add comment about z-index

Co-authored-by: Justin Domingue <domingue.justin@gmail.com>
2021-05-12 13:02:52 -04:00
Moody Salem
285e4f28f5 fix bug in fiat value price impact display 2021-05-12 11:56:18 -05:00
Moody Salem
3aa045303a remove a few unused isToken calls 2021-05-12 11:43:30 -05:00
Noah Zinsmeister
e0a7c3794e bump gas margin to 20% 2021-05-12 12:35:46 -04:00
Noah Zinsmeister
f5fc5da341 fix erroneous addition of tokensOwned{0,1} (#1533)
only pass tokenId to useV3PositionFees
2021-05-12 12:20:20 -04:00
Moody Salem
bea5c0484b fix lint errors, show source list in extra tokens 2021-05-12 09:32:24 -05:00
Moody Salem
27960532ca refactor: use new sdk version (#1472)
* do not construct tokens for wrapped token info

* some cleanup of the wrapped token info

* back to extends, bump sdk core version via v2/v3 sdk updates

* Revert "back to extends, bump sdk core version via v2/v3 sdk updates"

This reverts commit 92cc5073

* update the sdk version

* fix some more uses of instanceof

* finish the refactor

* mess with the currency list performance

* start replacing with the latest v3/v2 sdks

* raw -> quotient

* more cleanup

* finish the refactor

* clean up currency list refactor

* fix list rendering

* perf(token lists): improve app performance when there are large inactive token lists (#1510)

* improve inactive token lists performance

* cleanup before larger changes to combine inactive lists

* only do the search if the query does not match any active lists, limit the number of results

* more performance improvements

* search inactive lists more aggressively
2021-05-12 08:52:17 -05:00
Ian Lapham
37a4e2f6e3 More UI bug fixes (#1515)
* fix for error token map parsings

* update varios UI styles

* update padding on & amounts
2021-05-11 22:29:25 -04:00
Moody Salem
19a3b12ca8 bump typechain for faster/less noisy type generation 2021-05-11 16:43:08 -05:00
Noah Zinsmeister
22c1ddf393 swaps to .5% slippage 2021-05-11 15:00:51 -04:00
Noah Zinsmeister
b44ae1a267 clean up parseCurrencyFromURLParameter 2021-05-11 14:19:56 -04:00
Ian Lapham
418dcf0cb2 Various bug fixes (#1501)
* fix for error token map parsings

* fix error on formatting sig figs

* fix various bugs

* no hover cursor

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-05-11 14:03:02 -04:00
Noah Zinsmeister
58a508c9d6 .25% -> .30% slippage for v3 swaps 2021-05-11 13:53:15 -04:00
Jorropo
3198129af2 feat(routing): support mirror protocol routing as additional bases (#1375)
* feat(routing): support mirror protocol routing as additional bases

* Fix code style issues with ESLint

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
2021-05-11 13:28:50 -04:00
John Shutt
89d484d882 feat(uma): uma call option routing (#1385)
* feat(uma): uma call option routing

Signed-off-by: John Shutt <john.d.shutt@gmail.com>

* Fix code style issues with ESLint

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
Co-authored-by: Moody Salem <moodysalem@users.noreply.github.com>
2021-05-11 13:25:51 -04:00
Callil Capuozzo
fa4688d96c UI improvements (#1505)
* Change price ratio using slash to "per"

* Fix header, toggle copy and increase copy

* Add clearer V2 and migrate buttons

* Fix link

* fix account modal background color

* tweak sig figs

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-05-11 13:25:04 -04:00
Moody Salem
7ee761a59e feat: automatic slippage tolerance (#1463)
* automatic slippage tolerance start

* get it compiling

* out of range/in range behavior of slippage tolerance in add

* small useDerivedSwapInfo refactor

* improve useSwapSlippageTolerance

* fix unit test

* thread placeholder slippage through

* small improvement to slippage input behavior

* fix the display bug

* fix tx settings modal ux

* don't pass props unnecessarily

* switch back to static swap slippage for now

bump migrate slippage to .75%

* fix font size

* add flag for auto slippage migration

validate version updates even more

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-05-11 13:00:42 -04:00
jochenboesmans
78e95f6073 Add App-level error boundary, referring users to GitHub issue creation (#1464)
* Add App-level error boundary, referring users to GitHub issue creation on page crashes. (#1452)

* Class component is used as boundary since catching errors is apparently not yet possible with hooks.

* EventListener in window was removed and replaced by error boundary's error catch, which now fires a GA exception. The fields it passes are slightly different because React uses slightly different error types.

* Pre-filling issues with dynamic data is possible with POST requests to GitHub's API, but the GH web client seems to only support pre-fill based on templates. Therefore users still need to copy error info themselves.

* Prefill GitHub issues with crash data.

* Added package 'react-device-detect' to include device data such as OS, browser etc. in crash report.
* Included error stack in issue body.
* Used <code> html tag for displaying stack to user.

* Slightly reduce vertical padding on code block.

* Add ua-parser-js for parsing user agent.

* Revert react-device-detect to ^1.6.2 (which is used for mobile detection etc. in components)
2021-05-11 12:09:01 -04:00
Noah Zinsmeister
c67e57505a make price sig figs more consistent 2021-05-11 10:17:06 -04:00
Justin Domingue
30f7385db7 optimize sandtexture.png with .webp (#1502)
Co-authored-by: Justin Domingue <domingue.justin@gmail.com>
2021-05-10 21:32:53 -04:00
Noah Zinsmeister
c0f58ae810 don't use a signer for callStatic contract 2021-05-10 16:59:11 -04:00
Noah Zinsmeister
54dd5476ca fetch fees directly from collect via callStatic (#1500)
* fetch fees directly from collect via callStatic

* don't clear state
2021-05-10 16:30:02 -04:00
Moody Salem
57786335df fix calculateSlippageAmount (#1497) 2021-05-10 14:22:26 -05:00
Joe Butler
948e01a196 Fix typo (#1454) 2021-05-10 14:01:35 -04:00
127 changed files with 1875 additions and 1253 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

@@ -9,6 +9,7 @@
"@popperjs/core": "^2.4.4",
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",
"@react-hook/window-scroll": "^1.3.0",
"@reduxjs/toolkit": "^1.3.5",
"@storybook/addon-actions": "^6.1.17",
"@storybook/addon-essentials": "^6.1.17",
@@ -20,7 +21,7 @@
"@storybook/react": "^6.1.17",
"@storybook/theming": "^6.1.17",
"@styled-system/css": "^5.1.5",
"@typechain/ethers-v5": "^6.0.5",
"@typechain/ethers-v5": "^7.0.0",
"@types/jest": "^25.2.1",
"@types/lodash.flatmap": "^4.5.6",
"@types/luxon": "^1.24.4",
@@ -36,6 +37,7 @@
"@types/rebass": "^4.0.7",
"@types/styled-components": "^5.1.0",
"@types/testing-library__cypress": "^5.0.5",
"@types/ua-parser-js": "^0.7.35",
"@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^4.1.0",
@@ -46,10 +48,10 @@
"@uniswap/token-lists": "^1.0.0-beta.19",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v2-sdk": "^1.0.9",
"@uniswap/v2-sdk": "^3.0.0-alpha.0",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "1.0.0",
"@uniswap/v3-sdk": "^1.0.8",
"@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",
@@ -103,8 +105,9 @@
"start-server-and-test": "^1.11.0",
"styled-components": "^4.2.0",
"styled-system": "^5.1.5",
"typechain": "^4.0.3",
"typechain": "^5.0.0",
"typescript": "^4.2.3",
"ua-parser-js": "^0.7.28",
"use-count-up": "^2.2.5",
"wcag-contrast": "^3.0.0",
"workbox-core": "^6.1.0",
@@ -115,8 +118,8 @@
},
"scripts": {
"compile-contract-types": "yarn compile-external-abi-types && yarn compile-v3-contract-types",
"compile-external-abi-types": "npx typechain --target ethers-v5 --outDir src/abis/types './src/abis/**/*.json'",
"compile-v3-contract-types": "npx typechain --target ethers-v5 --outDir src/types/v3 './node_modules/@uniswap/?(v3-core|v3-periphery)/artifacts/contracts/**/*.json'",
"compile-external-abi-types": "npx typechain --target ethers-v5 --out-dir src/abis/types './src/abis/**/*.json'",
"compile-v3-contract-types": "npx typechain --target ethers-v5 --out-dir src/types/v3 './node_modules/@uniswap/?(v3-core|v3-periphery)/artifacts/contracts/**/*.json'",
"build": "yarn compile-contract-types && react-scripts build",
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'",
"postinstall": "yarn compile-contract-types",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

@@ -76,7 +76,6 @@ const AccountGroupingRow = styled.div`
`
const AccountSection = styled.div`
background-color: ${({ theme }) => theme.bg1};
padding: 0rem 1rem;
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
`

View File

@@ -46,7 +46,7 @@ export default function RangeBadge({
<BadgeWrapper>
{removed ? (
<MouseoverTooltip text={`Your position has 0 liquidity, and is not earning fees.`}>
<Badge variant={BadgeVariant.WARNING_OUTLINE}>
<Badge variant={BadgeVariant.DEFAULT}>
<AlertCircle width={14} height={14} />
&nbsp;
<BadgeText>{t('Inactive')}</BadgeText>

View File

@@ -1,14 +1,15 @@
import { CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import React, { useMemo } from 'react'
import useTheme from '../../hooks/useTheme'
import { TYPE } from '../../theme'
import { warningSeverity } from '../../utils/prices'
import HoverInlineText from 'components/HoverInlineText'
export function FiatValue({
fiatValue,
priceImpact,
}: {
fiatValue: CurrencyAmount | null | undefined
fiatValue: CurrencyAmount<Currency> | null | undefined
priceImpact?: Percent
}) {
const theme = useTheme()
@@ -23,9 +24,10 @@ export function FiatValue({
return (
<TYPE.body fontSize={14} color={fiatValue ? theme.text2 : theme.text4}>
{fiatValue ? '~' : ''}${fiatValue ? Number(fiatValue?.toSignificant(6)).toLocaleString('en') : '-'}
{fiatValue ? '~' : ''}$
<HoverInlineText text={fiatValue ? fiatValue?.toSignificant(6, { groupSeparator: ',' }) : '-'} />{' '}
{priceImpact ? (
<span style={{ color: priceImpactColor }}> ({priceImpact.multiply(-100).toSignificant(3)}%)</span>
<span style={{ color: priceImpactColor }}> ({priceImpact.multiply(-1).toSignificant(3)}%)</span>
) : null}
</TYPE.body>
)

View File

@@ -1,5 +1,5 @@
import { Pair } from '@uniswap/v2-sdk'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import React, { useState, useCallback } from 'react'
import styled from 'styled-components'
import { darken } from 'polished'
@@ -18,6 +18,7 @@ import useTheme from '../../hooks/useTheme'
import { Lock } from 'react-feather'
import { AutoColumn } from 'components/Column'
import { FiatValue } from './FiatValue'
import { formatTokenAmount } from 'utils/formatTokenAmount'
const InputPanel = styled.div<{ hideInput?: boolean }>`
${({ theme }) => theme.flexColumnNoWrap}
@@ -134,11 +135,7 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
margin-left: 0.25rem;
:hover {
/* border: 1px solid ${({ theme }) => theme.primary1}; */
}
:focus {
/* border: 1px solid ${({ theme }) => theme.primary1}; */
outline: none;
}
@@ -159,7 +156,7 @@ interface CurrencyInputPanelProps {
pair?: Pair | null
hideInput?: boolean
otherCurrency?: Currency | null
fiatValue?: CurrencyAmount | null
fiatValue?: CurrencyAmount<Token> | null
priceImpact?: Percent
id: string
showCommonBases?: boolean
@@ -273,7 +270,7 @@ export default function CurrencyInputPanel({
>
{!hideBalance && !!currency && selectedCurrencyBalance
? (customBalanceText ?? 'Balance: ') +
selectedCurrencyBalance?.toSignificant(4) +
formatTokenAmount(selectedCurrencyBalance, 4) +
' ' +
currency.symbol
: '-'}
@@ -285,7 +282,6 @@ export default function CurrencyInputPanel({
) : (
'-'
)}
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
</RowBetween>
</FiatRow>

View File

@@ -1,9 +1,9 @@
import { ChainId, Currency, ETHER, Token } from '@uniswap/sdk-core'
import { ChainId, Currency } from '@uniswap/sdk-core'
import React, { useMemo } from 'react'
import styled from 'styled-components'
import EthereumLogo from '../../assets/images/ethereum-logo.png'
import useHttpLocations from '../../hooks/useHttpLocations'
import { WrappedTokenInfo } from '../../state/lists/hooks'
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
import Logo from '../Logo'
export const getTokenLogoURL = (address: string) =>
@@ -37,9 +37,9 @@ export default function CurrencyLogo({
const uriLocations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined)
const srcs: string[] = useMemo(() => {
if (currency === ETHER) return []
if (!currency || currency.isEther) return []
if (currency instanceof Token) {
if (currency.isToken) {
const defaultUrls = currency.chainId === ChainId.MAINNET ? [getTokenLogoURL(currency.address)] : []
if (currency instanceof WrappedTokenInfo) {
return [...uriLocations, ...defaultUrls]
@@ -49,7 +49,7 @@ export default function CurrencyLogo({
return []
}, [currency, uriLocations])
if (currency === ETHER) {
if (currency?.isEther) {
return <StyledEthereumLogo src={EthereumLogo} size={size} style={style} {...rest} />
}

View File

@@ -0,0 +1,162 @@
import React, { ErrorInfo } from 'react'
import { ExternalLink, ThemedBackground, TYPE } from '../../theme'
import { AutoColumn } from '../Column'
import styled from 'styled-components'
import ReactGA from 'react-ga'
import { getUserAgent } from '../../utils/getUserAgent'
import { AutoRow } from '../Row'
const FallbackWrapper = styled.div`
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
z-index: 1;
`
const BodyWrapper = styled.div<{ margin?: string }>`
padding: 1rem;
width: 100%;
white-space: ;
`
const CodeBlockWrapper = styled.div`
background: ${({ theme }) => theme.bg0};
overflow: auto;
white-space: pre;
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 24px;
padding: 18px 24px;
color: ${({ theme }) => theme.text1};
`
const LinkWrapper = styled.div`
color: ${({ theme }) => theme.blue1};
padding: 6px 24px;
`
const SomethingWentWrongWrapper = styled.div`
padding: 6px 24px;
`
type ErrorBoundaryState = {
error: Error | null
}
export default class ErrorBoundary extends React.Component<unknown, ErrorBoundaryState> {
constructor(props: unknown) {
super(props)
this.state = { error: null }
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { error }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
ReactGA.exception({
...error,
...errorInfo,
fatal: true,
})
}
render() {
const { error } = this.state
if (error !== null) {
const encodedBody = encodeURIComponent(issueBody(error))
return (
<FallbackWrapper>
<ThemedBackground />
<BodyWrapper>
<AutoColumn gap={'md'}>
<SomethingWentWrongWrapper>
<TYPE.label fontSize={24} fontWeight={600}>
Something went wrong
</TYPE.label>
</SomethingWentWrongWrapper>
<CodeBlockWrapper>
<code>
<TYPE.main fontSize={10}>{error.stack}</TYPE.main>
</code>
</CodeBlockWrapper>
<AutoRow>
<LinkWrapper>
<ExternalLink
id="create-github-issue-link"
href={`https://github.com/Uniswap/uniswap-interface/issues/new?assignees=&labels=bug&body=${encodedBody}&title=${encodeURIComponent(
`Crash report: \`${error.name}${error.message && `: ${error.message}`}\``
)}`}
target="_blank"
>
<TYPE.link fontSize={16}>
Create an issue on GitHub
<span></span>
</TYPE.link>
</ExternalLink>
</LinkWrapper>
<LinkWrapper>
<ExternalLink id="get-support-on-discord" href="https://discord.gg/FCfyBSbCU5" target="_blank">
<TYPE.link fontSize={16}>
Get support on Discord
<span></span>
</TYPE.link>
</ExternalLink>
</LinkWrapper>
</AutoRow>
</AutoColumn>
</BodyWrapper>
</FallbackWrapper>
)
}
return this.props.children
}
}
function issueBody(error: Error): string {
if (!error) throw new Error('no error to report')
const deviceData = getUserAgent()
return `**Bug Description**
App crashed
**Steps to Reproduce**
1. Go to ...
2. Click on ...
...
**URL**
${window.location.href}
${
error.name &&
`**Error**
\`\`\`
${error.name}${error.message && `: ${error.message}`}
\`\`\`
`
}
${
error.stack &&
`**Stacktrace**
\`\`\`
${error.stack}
\`\`\`
`
}
${
deviceData &&
`**Device data**
\`\`\`json5
${JSON.stringify(deviceData, null, 2)}
\`\`\`
`
}
`
}

View File

@@ -1,6 +1,6 @@
import JSBI from 'jsbi'
import React from 'react'
import { CurrencyAmount, Fraction } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import { Currency, CurrencyAmount, Fraction } from '@uniswap/sdk-core'
const CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))
@@ -8,7 +8,7 @@ export default function FormattedCurrencyAmount({
currencyAmount,
significantDigits = 4,
}: {
currencyAmount: CurrencyAmount
currencyAmount: CurrencyAmount<Currency>
significantDigits?: number
}) {
return (

View File

@@ -1,4 +1,4 @@
import { ChainId, TokenAmount } from '@uniswap/sdk-core'
import { ChainId, CurrencyAmount, Token } from '@uniswap/sdk-core'
import React, { useMemo } from 'react'
import { X } from 'react-feather'
import styled from 'styled-components'
@@ -45,14 +45,14 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
const uni = chainId ? UNI[chainId] : undefined
const total = useAggregateUniBalance()
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, uni)
const uniToClaim: TokenAmount | undefined = useTotalUniEarned()
const uniBalance: CurrencyAmount<Token> | undefined = useTokenBalance(account ?? undefined, uni)
const uniToClaim: CurrencyAmount<Token> | undefined = useTotalUniEarned()
const totalSupply: TokenAmount | undefined = useTotalSupply(uni)
const totalSupply: CurrencyAmount<Token> | undefined = useTotalSupply(uni)
const uniPrice = useUSDCPrice(uni)
const blockTimestamp = useCurrentBlockTimestamp()
const unclaimedUni = useTokenBalance(useMerkleDistributorContract()?.address, uni)
const circulation: TokenAmount | undefined = useMemo(
const circulation: CurrencyAmount<Token> | undefined = useMemo(
() =>
blockTimestamp && uni && chainId === ChainId.MAINNET
? computeUniCirculation(uni, blockTimestamp, unclaimedUni)

View File

@@ -1,4 +1,5 @@
import { ChainId } from '@uniswap/sdk-core'
import useScrollPosition from '@react-hook/window-scroll'
import React, { useState } from 'react'
import { Text } from 'rebass'
import { NavLink } from 'react-router-dom'
@@ -29,7 +30,7 @@ import { Dots } from '../swap/styleds'
import Modal from '../Modal'
import UniBalanceContent from './UniBalanceContent'
const HeaderFrame = styled.div`
const HeaderFrame = styled.div<{ showBackground: boolean }>`
display: grid;
grid-template-columns: 120px 1fr 120px;
align-items: center;
@@ -39,12 +40,17 @@ const HeaderFrame = styled.div`
width: 100%;
top: 0;
position: relative;
/* border-bottom: 1px solid ${({ theme }) => theme.bg2}; */
padding: 1rem;
z-index: 21;
/* background-color: ${({ theme }) => theme.bg1}; */
position: relative;
/* Background slide effect on scroll. */
background-image: ${({ theme }) => `linear-gradient(to bottom, transparent 50%, ${theme.bg0} 50% )}}`}
background-position: ${({ showBackground }) => (showBackground ? '0 -100%' : '0 0')};
background-size: 100% 200%;
box-shadow: 0px 0px 0px 1px ${({ theme, showBackground }) => (showBackground ? theme.bg2 : 'transparent;')};
transition: background-position .1s, box-shadow .1s;
${({ theme }) => theme.mediaWidth.upToMedium`
padding: 1rem;
grid-template-columns: 120px 1fr;
@@ -90,7 +96,7 @@ const HeaderElement = styled.div`
}
${({ theme }) => theme.mediaWidth.upToMedium`
flex-direction: row-reverse;
flex-direction: row-reverse;
align-items: center;
`};
`
@@ -116,6 +122,9 @@ const HeaderLinks = styled(Row)`
grid-auto-flow: column;
grid-gap: 10px;
overflow: auto;
${({ theme }) => theme.mediaWidth.upToMedium`
justify-self: flex-end;
`};
`
const AccountElement = styled.div<{ active: boolean }>`
@@ -316,8 +325,10 @@ export default function Header() {
const [showUniBalanceModal, setShowUniBalanceModal] = useState(false)
const showClaimPopup = useShowClaimPopup()
const scrollY = useScrollPosition()
return (
<HeaderFrame>
<HeaderFrame showBackground={scrollY > 45}>
<ClaimModal />
<Modal isOpen={showUniBalanceModal} onDismiss={() => setShowUniBalanceModal(false)}>
<UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} />

View File

@@ -0,0 +1,63 @@
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 }>`
cursor: auto;
margin-left: ${({ margin }) => margin && '4px'};
color: ${({ theme, link }) => (link ? theme.blue1 : theme.text1)};
font-size: ${({ fontSize }) => fontSize ?? 'inherit'};
@media screen and (max-width: 600px) {
font-size: ${({ adjustSize }) => adjustSize && '12px'};
}
`
const HoverInlineText = ({
text,
maxCharacters = 20,
margin = false,
adjustSize = false,
fontSize,
link,
...rest
}: {
text: string
maxCharacters?: number
margin?: boolean
adjustSize?: boolean
fontSize?: string
link?: boolean
}) => {
const [showHover, setShowHover] = useState(false)
if (!text) {
return <span />
}
if (text.length > maxCharacters) {
return (
<Tooltip text={text} show={showHover}>
<TextWrapper
onMouseEnter={() => setShowHover(true)}
onMouseLeave={() => setShowHover(false)}
margin={margin}
adjustSize={adjustSize}
link={link}
fontSize={fontSize}
{...rest}
>
{' ' + text.slice(0, maxCharacters - 1) + '...'}
</TextWrapper>
</Tooltip>
)
}
return (
<TextWrapper margin={margin} adjustSize={adjustSize} link={link} fontSize={fontSize} {...rest}>
{text}
</TextWrapper>
)
}
export default HoverInlineText

View File

@@ -139,7 +139,7 @@ const StepCounter = ({
}}
/>
<InputTitle fontSize={12} textAlign="center">
{tokenB + ' / ' + tokenA}
{tokenB + ' per ' + tokenA}
</InputTitle>
</AutoColumn>
{!locked ? (

View File

@@ -3,6 +3,7 @@ import styled from 'styled-components'
import { darken } from 'polished'
import { useTranslation } from 'react-i18next'
import { NavLink, Link as HistoryLink } from 'react-router-dom'
import { Percent } from '@uniswap/sdk-core'
import { ArrowLeft } from 'react-feather'
import { RowBetween } from '../Row'
@@ -80,7 +81,6 @@ export function FindPoolTabs({ origin }: { origin: string }) {
<StyledArrowLeft />
</HistoryLink>
<ActiveText>Import Pool</ActiveText>
<SettingsTab />
</RowBetween>
</Tabs>
)
@@ -90,10 +90,12 @@ export function AddRemoveTabs({
adding,
creating,
positionID,
defaultSlippage,
}: {
adding: boolean
creating: boolean
positionID?: string | undefined
defaultSlippage: Percent
}) {
const theme = useTheme()
@@ -118,7 +120,7 @@ export function AddRemoveTabs({
<TYPE.mediumHeader fontWeight={500} fontSize={20}>
{creating ? 'Create a pair' : adding ? 'Add Liquidity' : 'Remove Liquidity'}
</TYPE.mediumHeader>
<SettingsTab />
<SettingsTab placeholderSlippage={defaultSlippage} />
</RowBetween>
</Tabs>
)

View File

@@ -1,4 +1,4 @@
import { TokenAmount } from '@uniswap/sdk-core'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import React, { useEffect } from 'react'
import { X } from 'react-feather'
import styled, { keyframes } from 'styled-components'
@@ -65,7 +65,7 @@ export default function ClaimPopup() {
// const userHasAvailableclaim = useUserHasAvailableClaim()
const userHasAvailableclaim: boolean = useUserHasAvailableClaim(account)
const unclaimedAmount: TokenAmount | undefined = useUserUnclaimedAmount(account)
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
// listen for available claim and show popup if needed
useEffect(() => {

View File

@@ -1,6 +1,7 @@
import JSBI from 'jsbi'
import React, { useState } from 'react'
import { Percent, TokenAmount } from '@uniswap/sdk-core'
import { JSBI, Pair } from '@uniswap/v2-sdk'
import { Percent, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { ChevronDown, ChevronUp } from 'react-feather'
import { Link } from 'react-router-dom'
import { Text } from 'rebass'
@@ -38,7 +39,7 @@ interface PositionCardProps {
pair: Pair
showUnwrapped?: boolean
border?: string
stakedBalance?: TokenAmount // optional balance to indicate that liquidity is deposited in mining pool
stakedBalance?: CurrencyAmount<Token> // optional balance to indicate that liquidity is deposited in mining pool
}
export default function V2PositionCard({ pair, border, stakedBalance }: PositionCardProps) {
@@ -56,8 +57,10 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
const userPoolBalance = stakedBalance ? userDefaultPoolBalance?.add(stakedBalance) : userDefaultPoolBalance
const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
!!userPoolBalance &&
!!totalPoolTokens &&
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
? new Percent(userPoolBalance.quotient, totalPoolTokens.quotient)
: undefined
const [token0Deposited, token1Deposited] =
@@ -65,7 +68,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
!!totalPoolTokens &&
!!userPoolBalance &&
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
? [
pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false),
@@ -174,7 +177,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
</Text>
</FixedHeightRow>
{userDefaultPoolBalance && JSBI.greaterThan(userDefaultPoolBalance.raw, BIG_INT_ZERO) && (
{userDefaultPoolBalance && JSBI.greaterThan(userDefaultPoolBalance.quotient, BIG_INT_ZERO) && (
<RowBetween marginTop="10px">
<ButtonPrimary
padding="8px"

View File

@@ -1,5 +1,6 @@
import { Percent, TokenAmount } from '@uniswap/sdk-core'
import { JSBI, Pair } from '@uniswap/v2-sdk'
import JSBI from 'jsbi'
import { Percent, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { darken } from 'polished'
import React, { useState } from 'react'
import { ChevronDown, ChevronUp } from 'react-feather'
@@ -49,7 +50,7 @@ interface PositionCardProps {
pair: Pair
showUnwrapped?: boolean
border?: string
stakedBalance?: TokenAmount // optional balance to indicate that liquidity is deposited in mining pool
stakedBalance?: CurrencyAmount<Token> // optional balance to indicate that liquidity is deposited in mining pool
}
export function MinimalPositionCard({ pair, showUnwrapped = false, border }: PositionCardProps) {
@@ -64,8 +65,10 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
const totalPoolTokens = useTotalSupply(pair.liquidityToken)
const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
!!userPoolBalance &&
!!totalPoolTokens &&
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
? new Percent(userPoolBalance.quotient, totalPoolTokens.quotient)
: undefined
const [token0Deposited, token1Deposited] =
@@ -73,7 +76,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
!!totalPoolTokens &&
!!userPoolBalance &&
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
? [
pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false),
@@ -82,7 +85,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
return (
<>
{userPoolBalance && JSBI.greaterThan(userPoolBalance.raw, JSBI.BigInt(0)) ? (
{userPoolBalance && JSBI.greaterThan(userPoolBalance.quotient, JSBI.BigInt(0)) ? (
<GreyCard border={border}>
<AutoColumn gap="12px">
<FixedHeightRow>
@@ -175,8 +178,10 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
const userPoolBalance = stakedBalance ? userDefaultPoolBalance?.add(stakedBalance) : userDefaultPoolBalance
const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
!!userPoolBalance &&
!!totalPoolTokens &&
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
? new Percent(userPoolBalance.quotient, totalPoolTokens.quotient)
: undefined
const [token0Deposited, token1Deposited] =
@@ -184,7 +189,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
!!totalPoolTokens &&
!!userPoolBalance &&
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
? [
pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false),
@@ -198,28 +203,23 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
<CardNoise />
<AutoColumn gap="12px">
<FixedHeightRow>
<AutoRow gap="8px">
<AutoRow gap="8px" style={{ marginLeft: '8px' }}>
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={20} />
<Text fontWeight={500} fontSize={20}>
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
</Text>
</AutoRow>
<RowFixed gap="8px">
<ButtonEmpty
padding="6px 8px"
borderRadius="12px"
width="fit-content"
onClick={() => setShowMore(!showMore)}
>
<RowFixed gap="8px" style={{ marginRight: '4px' }}>
<ButtonEmpty padding="6px 8px" borderRadius="12px" width="100%" onClick={() => setShowMore(!showMore)}>
{showMore ? (
<>
Manage
<ChevronUp size="20" style={{ marginLeft: '10px' }} />
<ChevronUp size="20" style={{ marginLeft: '8px', height: '20px', minWidth: '20px' }} />
</>
) : (
<>
Manage
<ChevronDown size="20" style={{ marginLeft: '10px' }} />
<ChevronDown size="20" style={{ marginLeft: '8px', height: '20px', minWidth: '20px' }} />
</>
)}
</ButtonEmpty>
@@ -301,14 +301,23 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
View accrued fees and analytics<span style={{ fontSize: '11px' }}></span>
</ExternalLink>
</ButtonSecondary>
{userDefaultPoolBalance && JSBI.greaterThan(userDefaultPoolBalance.raw, BIG_INT_ZERO) && (
{userDefaultPoolBalance && JSBI.greaterThan(userDefaultPoolBalance.quotient, BIG_INT_ZERO) && (
<RowBetween marginTop="10px">
<ButtonPrimary
padding="8px"
borderRadius="8px"
as={Link}
to={`/migrate/v2/${pair.liquidityToken.address}`}
width="32%"
>
Migrate
</ButtonPrimary>
<ButtonPrimary
padding="8px"
borderRadius="8px"
as={Link}
to={`/add/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
width="48%"
width="32%"
>
Add
</ButtonPrimary>
@@ -316,14 +325,14 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
padding="8px"
borderRadius="8px"
as={Link}
width="48%"
width="32%"
to={`/remove/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
>
Remove
</ButtonPrimary>
</RowBetween>
)}
{stakedBalance && JSBI.greaterThan(stakedBalance.raw, BIG_INT_ZERO) && (
{stakedBalance && JSBI.greaterThan(stakedBalance.quotient, BIG_INT_ZERO) && (
<ButtonPrimary
padding="8px"
borderRadius="8px"

View File

@@ -4,7 +4,7 @@
// import { basisPointsToPercent } from 'utils'
// import { DAI, WBTC } from '../../constants'
// import Component, { PositionListProps } from './index'
// import { TokenAmount } from '@uniswap/sdk-core'
// import { CurrencyAmount } from '@uniswap/sdk-core'
// import JSBI from 'jsbi'
// const FEE_BIPS = {
@@ -12,8 +12,8 @@
// THIRTY: basisPointsToPercent(30),
// ONE_HUNDRED: basisPointsToPercent(100),
// }
// const daiAmount = new TokenAmount(DAI, JSBI.BigInt(500 * 10 ** 18))
// const wbtcAmount = new TokenAmount(WBTC, JSBI.BigInt(10 ** 7))
// const daiAmount = CurrencyAmount.fromRawAmount(DAI, JSBI.BigInt(500 * 10 ** 18))
// const wbtcAmount = CurrencyAmount.fromRawAmount(WBTC, JSBI.BigInt(10 ** 7))
// const positions = [
// {
// feesEarned: {

View File

@@ -15,6 +15,7 @@ import { unwrappedToken } from 'utils/wrappedCurrency'
import { DAI, USDC, USDT, WBTC } from '../../constants'
import RangeBadge from 'components/Badge/RangeBadge'
import { RowFixed } from 'components/Row'
import HoverInlineText from 'components/HoverInlineText'
const Row = styled(Link)`
align-items: center;
@@ -120,8 +121,8 @@ export interface PositionListItemProps {
export function getPriceOrderingFromPositionForUI(
position?: Position
): {
priceLower?: Price
priceUpper?: Price
priceLower?: Price<Token, Token>
priceUpper?: Price<Token, Token>
quote?: Token
base?: Token
} {
@@ -129,8 +130,8 @@ export function getPriceOrderingFromPositionForUI(
return {}
}
const token0 = position.amount0.token
const token1 = position.amount1.token
const token0 = position.amount0.currency
const token1 = position.amount1.currency
// if token0 is a dollar-stable asset, set it as the quote token
const stables = [DAI, USDC, USDT]
@@ -244,8 +245,10 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
>
<RangeText>
<ExtentsText>Min: </ExtentsText>
{formatPrice(priceLower, 4)} {manuallyInverted ? currencyQuote?.symbol : currencyBase?.symbol} {' / '}{' '}
{manuallyInverted ? currencyBase?.symbol : currencyQuote?.symbol}
{formatPrice(priceLower, 5)}{' '}
<HoverInlineText text={manuallyInverted ? currencyQuote?.symbol ?? '' : currencyBase?.symbol ?? ''} />{' '}
{' per '}{' '}
<HoverInlineText text={manuallyInverted ? currencyBase?.symbol ?? '' : currencyQuote?.symbol ?? ''} />
</RangeText>{' '}
<HideSmall>
<DoubleArrow></DoubleArrow>{' '}
@@ -255,8 +258,13 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
</SmallOnly>
<RangeText>
<ExtentsText>Max:</ExtentsText>
{formatPrice(priceUpper, 4)} {manuallyInverted ? currencyQuote?.symbol : currencyBase?.symbol} {' / '}{' '}
{manuallyInverted ? currencyBase?.symbol : currencyQuote?.symbol}
{formatPrice(priceUpper, 5)}{' '}
<HoverInlineText text={manuallyInverted ? currencyQuote?.symbol ?? '' : currencyBase?.symbol ?? ''} />{' '}
{' per '}{' '}
<HoverInlineText
maxCharacters={10}
text={manuallyInverted ? currencyBase?.symbol ?? '' : currencyQuote?.symbol ?? ''}
/>
</RangeText>{' '}
</RangeLineItem>
</>

View File

@@ -13,7 +13,7 @@ import RateToggle from 'components/RateToggle'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import RangeBadge from 'components/Badge/RangeBadge'
import { ThemeContext } from 'styled-components'
import { JSBI } from '@uniswap/v2-sdk'
import JSBI from 'jsbi'
export const PositionPreview = ({
position,
@@ -135,7 +135,7 @@ export const PositionPreview = ({
<TYPE.main
textAlign="center"
fontSize="12px"
>{` ${quoteCurrency.symbol}/${baseCurrency.symbol}`}</TYPE.main>
>{` ${quoteCurrency.symbol} per ${baseCurrency.symbol}`}</TYPE.main>
<TYPE.small textAlign="center" color={theme.text3} style={{ marginTop: '4px' }}>
Your position will be 100% composed of {quoteCurrency?.symbol} at this price
</TYPE.small>
@@ -145,11 +145,11 @@ export const PositionPreview = ({
<LightCard padding="12px ">
<AutoColumn gap="4px" justify="center">
<TYPE.main fontSize="12px">Current price</TYPE.main>
<TYPE.mediumHeader>{`${price.toSignificant(6)} `}</TYPE.mediumHeader>
<TYPE.mediumHeader>{`${price.toSignificant(5)} `}</TYPE.mediumHeader>
<TYPE.main
textAlign="center"
fontSize="12px"
>{` ${quoteCurrency.symbol}/${baseCurrency.symbol}`}</TYPE.main>
>{` ${quoteCurrency.symbol} per ${baseCurrency.symbol}`}</TYPE.main>
</AutoColumn>
</LightCard>
</AutoColumn>

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { Currency, Price } from '@uniswap/sdk-core'
import { Currency, Price, Token } from '@uniswap/sdk-core'
import StepCounter from 'components/InputStepCounter/InputStepCounter'
import { RowBetween } from 'components/Row'
import { useActiveWeb3React } from 'hooks'
@@ -19,8 +19,8 @@ export default function RangeSelector({
currencyB,
feeAmount,
}: {
priceLower?: Price
priceUpper?: Price
priceLower?: Price<Token, Token>
priceUpper?: Price<Token, Token>
getDecrementLower: () => string
getIncrementLower: () => string
getDecrementUpper: () => string

View File

@@ -26,10 +26,10 @@ export default function RateToggle({
<div style={{ width: 'fit-content', display: 'flex', alignItems: 'center' }}>
<ToggleWrapper width="fit-content">
<ToggleElement isActive={isSorted} fontSize="12px" onClick={handleRateToggle}>
{isSorted ? currencyB.symbol + ' / ' + currencyA.symbol : currencyA.symbol + ' / ' + currencyB.symbol}{' '}
{isSorted ? currencyA.symbol + ' price ' : currencyB.symbol + ' price '}
</ToggleElement>
<ToggleElement isActive={!isSorted} fontSize="12px" onClick={handleRateToggle}>
{isSorted ? currencyA.symbol + ' / ' + currencyB.symbol : currencyB.symbol + ' / ' + currencyA.symbol}
{isSorted ? currencyB.symbol + ' price ' : currencyA.symbol + ' price '}
</ToggleElement>
</ToggleWrapper>
</div>

View File

@@ -49,7 +49,7 @@ export default function CommonBases({
onSelect(ETHER)
}
}}
disable={selectedCurrency === ETHER}
disable={selectedCurrency?.isEther}
>
<CurrencyLogo currency={ETHER} style={{ marginRight: 8 }} />
<Text fontWeight={500} fontSize={16}>
@@ -57,7 +57,7 @@ export default function CommonBases({
</Text>
</BaseWrapper>
{(typeof chainId === 'number' ? SUGGESTED_BASES[chainId] ?? [] : []).map((token: Token) => {
const selected = selectedCurrency instanceof Token && selectedCurrency.address === token.address
const selected = selectedCurrency?.isToken && selectedCurrency.address === token.address
return (
<BaseWrapper onClick={() => !selected && onSelect(token)} disable={selected} key={token.address}>
<CurrencyLogo currency={token} style={{ marginRight: 8 }} />

View File

@@ -1,13 +1,14 @@
import { Currency, CurrencyAmount, currencyEquals, ETHER, Token } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, currencyEquals, Token } from '@uniswap/sdk-core'
import React, { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'
import styled from 'styled-components'
import { useActiveWeb3React } from '../../hooks'
import { WrappedTokenInfo, useCombinedActiveList } from '../../state/lists/hooks'
import { useCombinedActiveList } from '../../state/lists/hooks'
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
import { useCurrencyBalance } from '../../state/wallet/hooks'
import { TYPE } from '../../theme'
import { useIsUserAddedToken, useAllInactiveTokens } from '../../hooks/Tokens'
import { useIsUserAddedToken } from '../../hooks/Tokens'
import Column from '../Column'
import { RowFixed, RowBetween } from '../Row'
import CurrencyLogo from '../CurrencyLogo'
@@ -23,7 +24,7 @@ import QuestionHelper from 'components/QuestionHelper'
import useTheme from 'hooks/useTheme'
function currencyKey(currency: Currency): string {
return currency instanceof Token ? currency.address : currency === ETHER ? 'ETHER' : ''
return currency.isToken ? currency.address : 'ETHER'
}
const StyledBalanceText = styled(Text)`
@@ -55,7 +56,7 @@ const FixedContentRow = styled.div`
align-items: center;
`
function Balance({ balance }: { balance: CurrencyAmount }) {
function Balance({ balance }: { balance: CurrencyAmount<Currency> }) {
return <StyledBalanceText title={balance.toExact()}>{balance.toSignificant(4)}</StyledBalanceText>
}
@@ -113,7 +114,7 @@ function CurrencyRow({
const { account } = useActiveWeb3React()
const key = currencyKey(currency)
const selectedTokenList = useCombinedActiveList()
const isOnSelectedList = isTokenOnList(selectedTokenList, currency)
const isOnSelectedList = isTokenOnList(selectedTokenList, currency.isToken ? currency : undefined)
const customAdded = useIsUserAddedToken(currency)
const balance = useCurrencyBalance(account ?? undefined, currency)
@@ -132,7 +133,7 @@ function CurrencyRow({
{currency.symbol}
</Text>
<TYPE.darkGray ml="0px" fontSize={'12px'} fontWeight={300}>
{currency.name} {!isOnSelectedList && customAdded && '• Added by user'}
{currency.name} {!currency.isEther && !isOnSelectedList && customAdded && '• Added by user'}
</TYPE.darkGray>
</Column>
<TokenTags currency={currency} />
@@ -143,84 +144,84 @@ function CurrencyRow({
)
}
const BREAK_LINE = 'BREAK'
type BreakLine = typeof BREAK_LINE
function isBreakLine(x: unknown): x is BreakLine {
return x === BREAK_LINE
}
function BreakLineComponent({ style }: { style: CSSProperties }) {
const theme = useTheme()
return (
<FixedContentRow style={style}>
<LightGreyCard padding="8px 12px" borderRadius="8px">
<RowBetween>
<RowFixed>
<TokenListLogoWrapper src={TokenListLogo} />
<TYPE.main ml="6px" fontSize="12px" color={theme.text1}>
Expanded results from inactive Token Lists
</TYPE.main>
</RowFixed>
<QuestionHelper text="Tokens from inactive lists. Import specific tokens below or click 'Manage' to activate more lists." />
</RowBetween>
</LightGreyCard>
</FixedContentRow>
)
}
export default function CurrencyList({
height,
currencies,
otherListTokens,
selectedCurrency,
onCurrencySelect,
otherCurrency,
fixedListRef,
showETH,
showImportView,
setImportToken,
breakIndex,
}: {
height: number
currencies: Currency[]
otherListTokens?: WrappedTokenInfo[]
selectedCurrency?: Currency | null
onCurrencySelect: (currency: Currency) => void
otherCurrency?: Currency | null
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
showETH: boolean
showImportView: () => void
setImportToken: (token: Token) => void
breakIndex: number | undefined
}) {
const itemData: (Currency | undefined)[] = useMemo(() => {
let formatted: (Currency | undefined)[] = showETH ? [Currency.ETHER, ...currencies] : currencies
if (breakIndex !== undefined) {
formatted = [...formatted.slice(0, breakIndex), undefined, ...formatted.slice(breakIndex, formatted.length)]
const itemData: (Currency | BreakLine)[] = useMemo(() => {
if (otherListTokens && otherListTokens?.length > 0) {
return [...currencies, BREAK_LINE, ...otherListTokens]
}
return formatted
}, [breakIndex, currencies, showETH])
return currencies
}, [currencies, otherListTokens])
const { chainId } = useActiveWeb3React()
const theme = useTheme()
const inactiveTokens: {
[address: string]: Token
} = useAllInactiveTokens()
const Row = useCallback(
({ data, index, style }) => {
const currency: Currency = data[index]
const isSelected = Boolean(selectedCurrency && currencyEquals(selectedCurrency, currency))
const otherSelected = Boolean(otherCurrency && currencyEquals(otherCurrency, currency))
const handleSelect = () => onCurrencySelect(currency)
function TokenRow({ data, index, style }) {
const row: Currency | BreakLine = data[index]
if (isBreakLine(row)) {
return <BreakLineComponent style={style} />
}
const currency = row
const isSelected = Boolean(currency && selectedCurrency && currencyEquals(selectedCurrency, currency))
const otherSelected = Boolean(currency && otherCurrency && currencyEquals(otherCurrency, currency))
const handleSelect = () => currency && onCurrencySelect(currency)
const token = wrappedCurrency(currency, chainId)
const showImport = inactiveTokens && token && Object.keys(inactiveTokens).includes(token.address)
if (index === breakIndex || !data) {
return (
<FixedContentRow style={style}>
<LightGreyCard padding="8px 12px" borderRadius="8px">
<RowBetween>
<RowFixed>
<TokenListLogoWrapper src={TokenListLogo} />
<TYPE.main ml="6px" fontSize="12px" color={theme.text1}>
Expanded results from inactive Token Lists
</TYPE.main>
</RowFixed>
<QuestionHelper text="Tokens from inactive lists. Import specific tokens below or click 'Manage' to activate more lists." />
</RowBetween>
</LightGreyCard>
</FixedContentRow>
)
}
const showImport = index > currencies.length
if (showImport && token) {
return (
<ImportRow
style={style}
token={token}
showImportView={showImportView}
setImportToken={setImportToken}
dim={true}
/>
<ImportRow style={style} token={token} showImportView={showImportView} setImportToken={setImportToken} dim />
)
} else {
} else if (currency) {
return (
<CurrencyRow
style={style}
@@ -230,22 +231,18 @@ export default function CurrencyList({
otherSelected={otherSelected}
/>
)
} else {
return null
}
},
[
chainId,
inactiveTokens,
onCurrencySelect,
otherCurrency,
selectedCurrency,
setImportToken,
showImportView,
breakIndex,
theme.text1,
]
[chainId, currencies.length, onCurrencySelect, otherCurrency, selectedCurrency, setImportToken, showImportView]
)
const itemKey = useCallback((index: number, data: any) => currencyKey(data[index]), [])
const itemKey = useCallback((index: number, data: typeof itemData) => {
const currency = data[index]
if (isBreakLine(currency)) return BREAK_LINE
return currencyKey(currency)
}, [])
return (
<FixedSizeList

View File

@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'
import { useActiveWeb3React } from '../../hooks'
import { useAllTokens, useToken, useIsUserAddedToken, useFoundOnInactiveList } from '../../hooks/Tokens'
import { useAllTokens, useToken, useIsUserAddedToken, useSearchInactiveTokenLists } from '../../hooks/Tokens'
import { CloseIcon, TYPE, ButtonText, IconWrapper } from '../../theme'
import { isAddress } from '../../utils'
import Column from '../Column'
@@ -94,11 +94,6 @@ export function CurrencySearch({
}
}, [isAddressSearch])
const showETH: boolean = useMemo(() => {
const s = debouncedQuery.toLowerCase().trim()
return s === '' || s === 'e' || s === 'et' || s === 'eth'
}, [debouncedQuery])
const tokenComparator = useTokenComparator(invertSearchOrder)
const filteredTokens: Token[] = useMemo(() => {
@@ -111,6 +106,14 @@ export function CurrencySearch({
const filteredSortedTokens = useSortedTokensByQuery(sortedTokens, debouncedQuery)
const filteredSortedTokensWithETH: Currency[] = useMemo(() => {
const s = debouncedQuery.toLowerCase().trim()
if (s === '' || s === 'e' || s === 'et' || s === 'eth') {
return [ETHER, ...filteredSortedTokens]
}
return filteredSortedTokens
}, [debouncedQuery, filteredSortedTokens])
const handleCurrencySelect = useCallback(
(currency: Currency) => {
onCurrencySelect(currency)
@@ -139,17 +142,17 @@ export function CurrencySearch({
const s = debouncedQuery.toLowerCase().trim()
if (s === 'eth') {
handleCurrencySelect(ETHER)
} else if (filteredSortedTokens.length > 0) {
} else if (filteredSortedTokensWithETH.length > 0) {
if (
filteredSortedTokens[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
filteredSortedTokens.length === 1
filteredSortedTokensWithETH[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
filteredSortedTokensWithETH.length === 1
) {
handleCurrencySelect(filteredSortedTokens[0])
handleCurrencySelect(filteredSortedTokensWithETH[0])
}
}
}
},
[filteredSortedTokens, handleCurrencySelect, debouncedQuery]
[filteredSortedTokensWithETH, handleCurrencySelect, debouncedQuery]
)
// menu ui
@@ -158,8 +161,9 @@ export function CurrencySearch({
useOnClickOutside(node, open ? toggle : undefined)
// if no results on main list, show option to expand into inactive
const inactiveTokens = useFoundOnInactiveList(debouncedQuery)
const filteredInactiveTokens: Token[] = useSortedTokensByQuery(inactiveTokens, debouncedQuery)
const filteredInactiveTokens = useSearchInactiveTokenLists(
filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch) ? debouncedQuery : undefined
)
return (
<ContentWrapper>
@@ -197,11 +201,8 @@ export function CurrencySearch({
{({ height }) => (
<CurrencyList
height={height}
showETH={showETH}
currencies={
filteredInactiveTokens ? filteredSortedTokens.concat(filteredInactiveTokens) : filteredSortedTokens
}
breakIndex={inactiveTokens && filteredSortedTokens ? filteredSortedTokens.length : undefined}
currencies={filteredSortedTokensWithETH}
otherListTokens={filteredInactiveTokens}
onCurrencySelect={handleCurrencySelect}
otherCurrency={otherSelectedCurrency}
selectedCurrency={selectedCurrency}

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

@@ -5,13 +5,12 @@ import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import { TYPE } from 'theme'
import ListLogo from 'components/ListLogo'
import { useActiveWeb3React } from 'hooks'
import { useCombinedInactiveList } from 'state/lists/hooks'
import useTheme from 'hooks/useTheme'
import { ButtonPrimary } from 'components/Button'
import styled from 'styled-components'
import { useIsUserAddedToken, useIsTokenActive } from 'hooks/Tokens'
import { CheckCircle } from 'react-feather'
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
const TokenSection = styled.div<{ dim?: boolean }>`
padding: 4px 20px;
@@ -53,18 +52,14 @@ export default function ImportRow({
showImportView: () => void
setImportToken: (token: Token) => void
}) {
// gloabls
const { chainId } = useActiveWeb3React()
const theme = useTheme()
// check if token comes from list
const inactiveTokenList = useCombinedInactiveList()
const list = chainId && inactiveTokenList?.[chainId]?.[token.address]?.list
// check if already active on list or local storage tokens
const isAdded = useIsUserAddedToken(token)
const isActive = useIsTokenActive(token)
const list = token instanceof WrappedTokenInfo ? token.list : undefined
return (
<TokenSection style={style}>
<CurrencyLogo currency={token} size={'24px'} style={{ opacity: dim ? '0.6' : '1' }} />

View File

@@ -1,3 +1,4 @@
import { TokenList } from '@uniswap/token-lists/dist/types'
import React from 'react'
import { Token, Currency } from '@uniswap/sdk-core'
import styled from 'styled-components'
@@ -15,7 +16,6 @@ import { useAddUserToken } from 'state/user/hooks'
import { getEtherscanLink } from 'utils'
import { useActiveWeb3React } from 'hooks'
import { ExternalLink } from '../../theme/components'
import { useCombinedInactiveList } from 'state/lists/hooks'
import ListLogo from 'components/ListLogo'
import { PaddedColumn } from './styleds'
@@ -41,28 +41,26 @@ const AddressText = styled(TYPE.blue)`
interface ImportProps {
tokens: Token[]
list?: TokenList
onBack?: () => void
onDismiss?: () => void
handleCurrencySelect?: (currency: Currency) => void
}
export function ImportToken({ tokens, onBack, onDismiss, handleCurrencySelect }: ImportProps) {
export function ImportToken({ tokens, list, onBack, onDismiss, handleCurrencySelect }: ImportProps) {
const theme = useTheme()
const { chainId } = useActiveWeb3React()
const addToken = useAddUserToken()
// use for showing import source on inactive tokens
const inactiveTokenList = useCombinedInactiveList()
return (
<Wrapper>
<PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}>
<RowBetween>
{onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div></div>}
{onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div />}
<TYPE.mediumHeader>Import {tokens.length > 1 ? 'Tokens' : 'Token'}</TYPE.mediumHeader>
{onDismiss ? <CloseIcon onClick={onDismiss} /> : <div></div>}
{onDismiss ? <CloseIcon onClick={onDismiss} /> : <div />}
</RowBetween>
</PaddedColumn>
<SectionBreak />
@@ -76,7 +74,6 @@ export function ImportToken({ tokens, onBack, onDismiss, handleCurrencySelect }:
</TYPE.body>
</AutoColumn>
{tokens.map((token) => {
const list = chainId && inactiveTokenList?.[chainId]?.[token.address]?.list
return (
<Card
backgroundColor={theme.bg2}

View File

@@ -1,14 +1,20 @@
import { TokenInfo } from '@uniswap/token-lists'
import { useMemo } from 'react'
import { isAddress } from '../../utils'
import { Token } from '@uniswap/sdk-core'
export function filterTokens(tokens: Token[], search: string): Token[] {
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
@@ -16,9 +22,7 @@ export function filterTokens(tokens: Token[], search: string): Token[] {
.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
@@ -29,10 +33,11 @@ export function filterTokens(tokens: Token[], search: string): Token[] {
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

@@ -1,9 +1,9 @@
import { Token, TokenAmount } from '@uniswap/sdk-core'
import { Token, CurrencyAmount, Currency } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { useAllTokenBalances } from '../../state/wallet/hooks'
// compare two token amounts with highest one coming first
function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) {
function balanceComparator(balanceA?: CurrencyAmount<Currency>, balanceB?: CurrencyAmount<Currency>) {
if (balanceA && balanceB) {
return balanceA.greaterThan(balanceB) ? -1 : balanceA.equalTo(balanceB) ? 0 : 1
} else if (balanceA && balanceA.greaterThan('0')) {
@@ -15,7 +15,7 @@ function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) {
}
function getTokenComparator(balances: {
[tokenAddress: string]: TokenAmount | undefined
[tokenAddress: string]: CurrencyAmount<Currency> | undefined
}): (tokenA: Token, tokenB: Token) => number {
return function sortTokens(tokenA: Token, tokenB: Token): number {
// -1 = a is first

View File

@@ -1,4 +1,3 @@
import JSBI from 'jsbi'
import React, { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga'
@@ -7,12 +6,7 @@ import styled, { ThemeContext } from 'styled-components'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks'
import {
useExpertModeManager,
useUserTransactionTTL,
useUserSlippageTolerance,
useUserSingleHopOnly,
} from '../../state/user/hooks'
import { useExpertModeManager, useUserSingleHopOnly } from '../../state/user/hooks'
import { TYPE } from '../../theme'
import { ButtonError } from '../Button'
import { AutoColumn } from '../Column'
@@ -21,6 +15,7 @@ import QuestionHelper from '../QuestionHelper'
import { RowBetween, RowFixed } from '../Row'
import Toggle from '../Toggle'
import TransactionSettings from '../TransactionSettings'
import { Percent } from '@uniswap/sdk-core'
const StyledMenuIcon = styled(Settings)`
height: 20px;
@@ -99,6 +94,8 @@ const MenuFlyout = styled.span`
${({ theme }) => theme.mediaWidth.upToMedium`
min-width: 18.125rem;
`};
user-select: none;
`
const Break = styled.div`
@@ -116,15 +113,12 @@ const ModalContentWrapper = styled.div`
border-radius: 20px;
`
export default function SettingsTab() {
export default function SettingsTab({ placeholderSlippage }: { placeholderSlippage: Percent }) {
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.SETTINGS)
const toggle = useToggleSettingsMenu()
const theme = useContext(ThemeContext)
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [ttl, setTtl] = useUserTransactionTTL()
const [expertMode, toggleExpertMode] = useExpertModeManager()
@@ -191,12 +185,7 @@ export default function SettingsTab() {
<Text fontWeight={600} fontSize={14}>
Transaction Settings
</Text>
<TransactionSettings
rawSlippage={JSBI.toNumber(userSlippageTolerance.numerator)}
setRawSlippage={setUserslippageTolerance}
deadline={ttl}
setDeadline={setTtl}
/>
<TransactionSettings placeholderSlippage={placeholderSlippage} />
<Text fontWeight={600} fontSize={14}>
Interface Settings
</Text>

View File

@@ -5,8 +5,8 @@ import Popover, { PopoverProps } from '../Popover'
const TooltipContainer = styled.div`
width: 256px;
padding: 0.6rem 1rem;
/* line-height: 150%; */
font-weight: 400;
word-break: break-word;
`
interface TooltipProps extends Omit<PopoverProps, 'content'> {

View File

@@ -1,17 +1,17 @@
import React, { useState, useRef, useContext } from 'react'
import React, { useState, useContext } from 'react'
import { Percent } from '@uniswap/sdk-core'
import styled, { ThemeContext } from 'styled-components'
import QuestionHelper from '../QuestionHelper'
import { TYPE } from '../../theme'
import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row'
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/index'
import { darken } from 'polished'
import { useSetUserSlippageTolerance, useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
enum SlippageError {
InvalidInput = 'InvalidInput',
RiskyLow = 'RiskyLow',
RiskyHigh = 'RiskyHigh',
}
enum DeadlineError {
@@ -64,7 +64,8 @@ const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean }
position: relative;
padding: 0 0.75rem;
flex: 1;
border: ${({ theme, active, warning }) => active && `1px solid ${warning ? theme.red1 : theme.primary1}`};
border: ${({ theme, active, warning }) =>
active ? `1px solid ${warning ? theme.red1 : theme.primary1}` : warning && `1px solid ${theme.red1}`};
:hover {
border: ${({ theme, active, warning }) =>
active && `1px solid ${warning ? darken(0.1, theme.red1) : darken(0.1, theme.primary1)}`};
@@ -85,63 +86,68 @@ const SlippageEmojiContainer = styled.span`
`}
`
export interface SlippageTabsProps {
rawSlippage: number
setRawSlippage: (rawSlippage: number) => void
deadline: number
setDeadline: (deadline: number) => void
export interface TransactionSettingsProps {
placeholderSlippage: Percent // varies according to the context in which the settings dialog is placed
}
export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, setDeadline }: SlippageTabsProps) {
export default function TransactionSettings({ placeholderSlippage }: TransactionSettingsProps) {
const theme = useContext(ThemeContext)
const inputRef = useRef<HTMLInputElement>()
const userSlippageTolerance = useUserSlippageTolerance()
const setUserSlippageTolerance = useSetUserSlippageTolerance()
const [deadline, setDeadline] = useUserTransactionTTL()
const [slippageInput, setSlippageInput] = useState('')
const [slippageError, setSlippageError] = useState<SlippageError | false>(false)
const [deadlineInput, setDeadlineInput] = useState('')
const [deadlineError, setDeadlineError] = useState<DeadlineError | false>(false)
const slippageInputIsValid =
slippageInput === '' || (rawSlippage / 100).toFixed(2) === Number.parseFloat(slippageInput).toFixed(2)
const deadlineInputIsValid = deadlineInput === '' || (deadline / 60).toString() === deadlineInput
let slippageError: SlippageError | undefined
if (slippageInput !== '' && !slippageInputIsValid) {
slippageError = SlippageError.InvalidInput
} else if (slippageInputIsValid && rawSlippage < 5) {
slippageError = SlippageError.RiskyLow
} else if (slippageInputIsValid && rawSlippage > 100) {
slippageError = SlippageError.RiskyHigh
} else {
slippageError = undefined
}
let deadlineError: DeadlineError | undefined
if (deadlineInput !== '' && !deadlineInputIsValid) {
deadlineError = DeadlineError.InvalidInput
} else {
deadlineError = undefined
}
function parseCustomSlippage(value: string) {
function parseSlippageInput(value: string) {
// populate what the user typed and clear the error
setSlippageInput(value)
setSlippageError(false)
try {
const valueAsIntFromRoundedFloat = Number.parseInt((Number.parseFloat(value) * 100).toString())
if (!Number.isNaN(valueAsIntFromRoundedFloat) && valueAsIntFromRoundedFloat < 5000) {
setRawSlippage(valueAsIntFromRoundedFloat)
if (value.length === 0) {
setUserSlippageTolerance('auto')
} else {
const parsed = Math.floor(Number.parseFloat(value) * 100)
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 5000) {
setUserSlippageTolerance('auto')
if (value !== '.') {
setSlippageError(SlippageError.InvalidInput)
}
} else {
setUserSlippageTolerance(new Percent(parsed, 10_000))
}
} catch {}
}
}
const tooLow = userSlippageTolerance !== 'auto' && userSlippageTolerance.lessThan(new Percent(5, 10_000))
const tooHigh = userSlippageTolerance !== 'auto' && userSlippageTolerance.greaterThan(new Percent(1, 100))
function parseCustomDeadline(value: string) {
// populate what the user typed and clear the error
setDeadlineInput(value)
setDeadlineError(false)
try {
const valueAsInt: number = Number.parseInt(value) * 60
if (!Number.isNaN(valueAsInt) && valueAsInt > 0) {
setDeadline(valueAsInt)
if (value.length === 0) {
setDeadline(DEFAULT_DEADLINE_FROM_NOW)
} else {
try {
const parsed: number = Math.floor(Number.parseFloat(value) * 60)
if (!Number.isInteger(parsed) || parsed < 60 || parsed > 180 * 60) {
setDeadlineError(DeadlineError.InvalidInput)
} else {
setDeadline(parsed)
}
} catch (error) {
console.error(error)
setDeadlineError(DeadlineError.InvalidInput)
}
} catch {}
}
}
return (
@@ -156,71 +162,56 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
<RowBetween>
<Option
onClick={() => {
setSlippageInput('')
setRawSlippage(10)
parseSlippageInput('')
}}
active={rawSlippage === 10}
active={userSlippageTolerance === 'auto'}
>
0.1%
Auto
</Option>
<Option
onClick={() => {
setSlippageInput('')
setRawSlippage(50)
}}
active={rawSlippage === 50}
>
0.5%
</Option>
<Option
onClick={() => {
setSlippageInput('')
setRawSlippage(100)
}}
active={rawSlippage === 100}
>
1%
</Option>
<OptionCustom active={![10, 50, 100].includes(rawSlippage)} warning={!slippageInputIsValid} tabIndex={-1}>
<OptionCustom active={userSlippageTolerance !== 'auto'} warning={!!slippageError} tabIndex={-1}>
<RowBetween>
{!!slippageInput &&
(slippageError === SlippageError.RiskyLow || slippageError === SlippageError.RiskyHigh) ? (
{tooLow || tooHigh ? (
<SlippageEmojiContainer>
<span role="img" aria-label="warning">
</span>
</SlippageEmojiContainer>
) : null}
{/* https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 */}
<Input
ref={inputRef as any}
placeholder={(rawSlippage / 100).toFixed(2)}
value={slippageInput}
placeholder={placeholderSlippage.toFixed(2)}
value={
slippageInput.length > 0
? slippageInput
: userSlippageTolerance === 'auto'
? ''
: userSlippageTolerance.toFixed(2)
}
onChange={(e) => parseSlippageInput(e.target.value)}
onBlur={() => {
parseCustomSlippage((rawSlippage / 100).toFixed(2))
setSlippageInput('')
setSlippageError(false)
}}
onChange={(e) => parseCustomSlippage(e.target.value)}
color={!slippageInputIsValid ? 'red' : ''}
color={slippageError ? 'red' : ''}
/>
%
</RowBetween>
</OptionCustom>
</RowBetween>
{!!slippageError && (
{slippageError || tooLow || tooHigh ? (
<RowBetween
style={{
fontSize: '14px',
paddingTop: '7px',
color: slippageError === SlippageError.InvalidInput ? 'red' : '#F3841E',
color: slippageError ? 'red' : '#F3841E',
}}
>
{slippageError === SlippageError.InvalidInput
{slippageError
? 'Enter a valid slippage percentage'
: slippageError === SlippageError.RiskyLow
: tooLow
? 'Your transaction may fail'
: 'Your transaction may be frontrun'}
</RowBetween>
)}
) : null}
</AutoColumn>
<AutoColumn gap="sm">
@@ -231,15 +222,22 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
<QuestionHelper text="Your transaction will revert if it is pending for more than this period of time." />
</RowFixed>
<RowFixed>
<OptionCustom style={{ width: '80px' }} tabIndex={-1}>
<OptionCustom style={{ width: '80px' }} warning={!!deadlineError} tabIndex={-1}>
<Input
color={!!deadlineError ? 'red' : undefined}
onBlur={() => {
parseCustomDeadline((deadline / 60).toString())
}}
placeholder={(deadline / 60).toString()}
value={deadlineInput}
placeholder={(DEFAULT_DEADLINE_FROM_NOW / 60).toString()}
value={
deadlineInput.length > 0
? deadlineInput
: deadline === DEFAULT_DEADLINE_FROM_NOW
? ''
: (deadline / 60).toString()
}
onChange={(e) => parseCustomDeadline(e.target.value)}
onBlur={() => {
setDeadlineInput('')
setDeadlineError(false)
}}
color={deadlineError ? 'red' : ''}
/>
</OptionCustom>
<TYPE.body style={{ paddingLeft: '8px' }} fontSize={14}>

View File

@@ -17,7 +17,7 @@ import { isAddress } from 'ethers/lib/utils'
import Confetti from '../Confetti'
import { CardNoise, CardBGImage, CardBGImageSmaller } from '../earn/styled'
import { useIsTransactionPending } from '../../state/transactions/hooks'
import { TokenAmount } from '@uniswap/sdk-core'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { getEtherscanLink, shortenAddress } from '../../utils'
const ContentWrapper = styled(AutoColumn)`
@@ -59,7 +59,7 @@ export default function AddressClaimModal({ isOpen, onDismiss }: { isOpen: boole
// monitor the status of the claim from contracts and txns
const { claimCallback } = useClaimCallback(parsedAddress)
const unclaimedAmount: TokenAmount | undefined = useUserUnclaimedAmount(parsedAddress)
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(parsedAddress)
// check if the user has something available
const hasAvailableClaim = useUserHasAvailableClaim(parsedAddress)

View File

@@ -1,5 +1,5 @@
import { TokenAmount } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import JSBI from 'jsbi'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { isAddress } from 'ethers/lib/utils'
import React, { useEffect, useState } from 'react'
import { Text } from 'rebass'
@@ -60,7 +60,7 @@ export default function ClaimModal() {
// monitor the status of the claim from contracts and txns
const { claimCallback } = useClaimCallback(account)
const unclaimedAmount: TokenAmount | undefined = useUserUnclaimedAmount(account)
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
const { claimSubmitted, claimTxn } = useUserHasSubmittedClaim(account ?? undefined)
const claimConfirmed = Boolean(claimTxn?.receipt)
@@ -116,12 +116,12 @@ export default function ClaimModal() {
)}
{userClaimData?.flags?.isLP &&
unclaimedAmount &&
JSBI.greaterThanOrEqual(unclaimedAmount.raw, nonLPAmount) && (
JSBI.greaterThanOrEqual(unclaimedAmount.quotient, nonLPAmount) && (
<RowBetween>
<TYPE.subHeader color="white">Liquidity</TYPE.subHeader>
<TYPE.subHeader color="white">
{unclaimedAmount
.subtract(new TokenAmount(unclaimedAmount.token, nonLPAmount))
.subtract(CurrencyAmount.fromRawAmount(unclaimedAmount.currency, nonLPAmount))
.toFixed(0, { groupSeparator: ',' })}{' '}
UNI
</TYPE.subHeader>

View File

@@ -4,8 +4,8 @@ import { RowBetween } from '../Row'
import styled from 'styled-components'
import { TYPE, StyledInternalLink } from '../../theme'
import DoubleCurrencyLogo from '../DoubleLogo'
import { ETHER, TokenAmount } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import { ButtonPrimary } from '../Button'
import { StakingInfo } from '../../state/stake/hooks'
import { useColor } from '../../hooks/useColor'
@@ -79,25 +79,25 @@ export default function PoolCard({ stakingInfo }: { stakingInfo: StakingInfo })
const isStaking = Boolean(stakingInfo.stakedAmount.greaterThan('0'))
// get the color of the token
const token = currency0 === ETHER ? token1 : token0
const WETH = currency0 === ETHER ? token0 : token1
const token = currency0.isEther ? token1 : token0
const WETH = currency0.isEther ? token0 : token1
const backgroundColor = useColor(token)
const totalSupplyOfStakingToken = useTotalSupply(stakingInfo.stakedAmount.token)
const totalSupplyOfStakingToken = useTotalSupply(stakingInfo.stakedAmount.currency)
const [, stakingTokenPair] = useV2Pair(...stakingInfo.tokens)
// let returnOverMonth: Percent = new Percent('0')
let valueOfTotalStakedAmountInWETH: TokenAmount | undefined
let valueOfTotalStakedAmountInWETH: CurrencyAmount<Token> | undefined
if (totalSupplyOfStakingToken && stakingTokenPair) {
// take the total amount of LP tokens staked, multiply by ETH value of all LP tokens, divide by all LP tokens
valueOfTotalStakedAmountInWETH = new TokenAmount(
valueOfTotalStakedAmountInWETH = CurrencyAmount.fromRawAmount(
WETH,
JSBI.divide(
JSBI.multiply(
JSBI.multiply(stakingInfo.totalStakedAmount.raw, stakingTokenPair.reserveOf(WETH).raw),
JSBI.multiply(stakingInfo.totalStakedAmount.quotient, stakingTokenPair.reserveOf(WETH).quotient),
JSBI.BigInt(2) // this is b/c the value of LP shares are ~double the value of the WETH they entitle owner to
),
totalSupplyOfStakingToken.raw
totalSupplyOfStakingToken.quotient
)
)
}

View File

@@ -11,7 +11,7 @@ import { ButtonConfirmed, ButtonError } from '../Button'
import ProgressCircles from '../ProgressSteps'
import CurrencyInputPanel from '../CurrencyInputPanel'
import { Pair } from '@uniswap/v2-sdk'
import { TokenAmount } from '@uniswap/sdk-core'
import { Token, CurrencyAmount } from '@uniswap/sdk-core'
import { useActiveWeb3React } from '../../hooks'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { usePairContract, useStakingContract } from '../../hooks/useContract'
@@ -40,7 +40,7 @@ interface StakingModalProps {
isOpen: boolean
onDismiss: () => void
stakingInfo: StakingInfo
userLiquidityUnstaked: TokenAmount | undefined
userLiquidityUnstaked: CurrencyAmount<Token> | undefined
}
export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiquidityUnstaked }: StakingModalProps) {
@@ -48,10 +48,14 @@ export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiqui
// track and parse user input
const [typedValue, setTypedValue] = useState('')
const { parsedAmount, error } = useDerivedStakeInfo(typedValue, stakingInfo.stakedAmount.token, userLiquidityUnstaked)
const { parsedAmount, error } = useDerivedStakeInfo(
typedValue,
stakingInfo.stakedAmount.currency,
userLiquidityUnstaked
)
const parsedAmountWrapped = wrappedCurrencyAmount(parsedAmount, chainId)
let hypotheticalRewardRate: TokenAmount = new TokenAmount(stakingInfo.rewardRate.token, '0')
let hypotheticalRewardRate: CurrencyAmount<Token> = CurrencyAmount.fromRawAmount(stakingInfo.rewardRate.currency, '0')
if (parsedAmountWrapped?.greaterThan('0')) {
hypotheticalRewardRate = stakingInfo.getHypotheticalRewardRate(
stakingInfo.stakedAmount.add(parsedAmountWrapped),
@@ -71,7 +75,10 @@ export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiqui
}, [onDismiss])
// pair contract for this token to be staked
const dummyPair = new Pair(new TokenAmount(stakingInfo.tokens[0], '0'), new TokenAmount(stakingInfo.tokens[1], '0'))
const dummyPair = new Pair(
CurrencyAmount.fromRawAmount(stakingInfo.tokens[0], '0'),
CurrencyAmount.fromRawAmount(stakingInfo.tokens[1], '0')
)
const pairContract = usePairContract(dummyPair.liquidityToken.address)
// approval data for stake
@@ -84,11 +91,11 @@ export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiqui
setAttempting(true)
if (stakingContract && parsedAmount && deadline) {
if (approval === ApprovalState.APPROVED) {
await stakingContract.stake(`0x${parsedAmount.raw.toString(16)}`, { gasLimit: 350000 })
await stakingContract.stake(`0x${parsedAmount.quotient.toString(16)}`, { gasLimit: 350000 })
} else if (signatureData) {
stakingContract
.stakeWithPermit(
`0x${parsedAmount.raw.toString(16)}`,
`0x${parsedAmount.quotient.toString(16)}`,
signatureData.deadline,
signatureData.v,
signatureData.r,
@@ -155,7 +162,7 @@ export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiqui
onUserInput={onUserInput}
onMax={handleMax}
showMaxButton={!atMaxAmount}
currency={stakingInfo.stakedAmount.token}
currency={stakingInfo.stakedAmount.currency}
pair={dummyPair}
label={''}
customBalanceText={'Available to deposit: '}

View File

@@ -1,8 +1,8 @@
import { Percent, Currency, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import React, { useContext } from 'react'
import { ThemeContext } from 'styled-components'
import { useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme'
import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage'
import { computeRealizedLPFeeAmount } from '../../utils/prices'
@@ -12,14 +12,14 @@ import FormattedPriceImpact from './FormattedPriceImpact'
import SwapRoute from './SwapRoute'
export interface AdvancedSwapDetailsProps {
trade?: V2Trade | V3Trade
trade?: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType>
allowedSlippage: Percent
}
export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
export function AdvancedSwapDetails({ trade, allowedSlippage }: AdvancedSwapDetailsProps) {
const theme = useContext(ThemeContext)
const realizedLPFee = computeRealizedLPFeeAmount(trade)
const [allowedSlippage] = useUserSlippageTolerance()
return !trade ? null : (
<AutoColumn gap="8px">
@@ -55,6 +55,17 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
<FormattedPriceImpact priceImpact={computePriceImpactWithMaximumSlippage(trade, allowedSlippage)} />
</TYPE.black>
</RowBetween>
<RowBetween>
<RowFixed>
<TYPE.black fontSize={12} fontWeight={400} color={theme.text2}>
Slippage tolerance
</TYPE.black>
</RowFixed>
<TYPE.black textAlign="right" fontSize={12} color={theme.text1}>
{allowedSlippage.toFixed(2)}%
</TYPE.black>
</RowBetween>
</AutoColumn>
)
}

View File

@@ -1,4 +1,4 @@
import { currencyEquals, Percent } from '@uniswap/sdk-core'
import { Currency, currencyEquals, Percent, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import React, { useCallback, useMemo } from 'react'
@@ -13,7 +13,11 @@ import SwapModalHeader from './SwapModalHeader'
* Returns true if the trade requires a confirmation of details before we can submit it
* @param args either a pair of V2 trades or a pair of V3 trades
*/
function tradeMeaningfullyDiffers(...args: [V2Trade, V2Trade] | [V3Trade, V3Trade]): boolean {
function tradeMeaningfullyDiffers(
...args:
| [V2Trade<Currency, Currency, TradeType>, V2Trade<Currency, Currency, TradeType>]
| [V3Trade<Currency, Currency, TradeType>, V3Trade<Currency, Currency, TradeType>]
): boolean {
const [tradeA, tradeB] = args
return (
tradeA.tradeType !== tradeB.tradeType ||
@@ -38,8 +42,8 @@ export default function ConfirmSwapModal({
txHash,
}: {
isOpen: boolean
trade: V2Trade | V3Trade | undefined
originalTrade: V2Trade | V3Trade | undefined
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | undefined
originalTrade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | undefined
attemptingTxn: boolean
txHash: string | undefined
recipient: string | null

View File

@@ -1,6 +1,5 @@
import { Percent } from '@uniswap/sdk-core'
import React from 'react'
import { ONE_BIPS } from '../../constants'
import { warningSeverity } from '../../utils/prices'
import { ErrorText, ErrorPill } from './styleds'
@@ -10,11 +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.lessThan(ONE_BIPS)
? `-${priceImpact.toFixed(2)}%`
: `${priceImpact.toFixed(2)}%`
: '-'}
{priceImpact ? `${priceImpact.multiply(-1).toFixed(2)}%` : '-'}
</ErrorText>
)
}
@@ -22,11 +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.lessThan(ONE_BIPS)
? `(-${priceImpact.toFixed(2)}%)`
: `(-${priceImpact.toFixed(2)}%)`
: '-'}
{priceImpact ? `(${priceImpact.multiply(-1).toFixed(2)}%)` : '-'}
</ErrorPill>
)
}

View File

@@ -1,6 +1,7 @@
import React from 'react'
import styled from 'styled-components'
import SettingsTab from '../Settings'
import { Percent } from '@uniswap/sdk-core'
import { RowBetween, RowFixed } from '../Row'
import { TYPE } from '../../theme'
@@ -11,7 +12,7 @@ const StyledSwapHeader = styled.div`
color: ${({ theme }) => theme.text2};
`
export default function SwapHeader() {
export default function SwapHeader({ allowedSlippage }: { allowedSlippage: Percent }) {
return (
<StyledSwapHeader>
<RowBetween>
@@ -21,9 +22,7 @@ export default function SwapHeader() {
</TYPE.black>
</RowFixed>
<RowFixed>
{/* <TradeInfo disabled={!trade} trade={trade} /> */}
{/* <div style={{ width: '8px' }}></div> */}
<SettingsTab />
<SettingsTab placeholderSlippage={allowedSlippage} />
</RowFixed>
</RowBetween>
</StyledSwapHeader>

View File

@@ -1,3 +1,4 @@
import { Currency, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
@@ -12,7 +13,7 @@ export default function SwapModalFooter({
swapErrorMessage,
disabledConfirm,
}: {
trade: V2Trade | V3Trade
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType>
onConfirm: () => void
swapErrorMessage: string | undefined
disabledConfirm: boolean

View File

@@ -1,4 +1,4 @@
import { Percent, TradeType } from '@uniswap/sdk-core'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import React, { useContext, useState } from 'react'
@@ -47,7 +47,7 @@ export default function SwapModalHeader({
showAcceptChanges,
onAcceptChanges,
}: {
trade: V2Trade | V3Trade
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType>
allowedSlippage: Percent
recipient: string | null
showAcceptChanges: boolean
@@ -135,7 +135,7 @@ export default function SwapModalHeader({
</RowBetween>
<LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}>
<AdvancedSwapDetails trade={trade} />
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} />
</LightCard>
{showAcceptChanges ? (

View File

@@ -1,3 +1,4 @@
import { Currency, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade, FeeAmount } from '@uniswap/v3-sdk'
import React, { Fragment, memo, useContext } from 'react'
@@ -14,7 +15,11 @@ function LabeledArrow({}: { fee: FeeAmount }) {
return <ChevronRight size={14} color={theme.text2} />
}
export default memo(function SwapRoute({ trade }: { trade: V2Trade | V3Trade }) {
export default memo(function SwapRoute({
trade,
}: {
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType>
}) {
const tokenPath = trade instanceof V2Trade ? trade.route.path : trade.route.tokenPath
const theme = useContext(ThemeContext)
return (
@@ -25,7 +30,7 @@ export default memo(function SwapRoute({ trade }: { trade: V2Trade | V3Trade })
return (
<Fragment key={i}>
<Flex alignItems="end">
<TYPE.black fontSize={14} color={theme.text1} ml="0.145rem" mr="0.145rem">
<TYPE.black color={theme.text1} ml="0.145rem" mr="0.145rem">
{currency.symbol}
</TYPE.black>
</Flex>

View File

@@ -1,12 +1,11 @@
import React, { useCallback } from 'react'
import { Price } from '@uniswap/sdk-core'
import { Price, Currency } from '@uniswap/sdk-core'
import { useContext } from 'react'
import { Text } from 'rebass'
import styled, { ThemeContext } from 'styled-components'
interface TradePriceProps {
price: Price
price: Price<Currency, Currency>
showInverted: boolean
setShowInverted: (showInverted: boolean) => void
}

View File

@@ -55,7 +55,7 @@ export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>`
? theme.yellow2
: severity === 1
? theme.text1
: theme.green1};
: theme.text2};
`
export const ErrorPill = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>`

View File

@@ -15,6 +15,7 @@ import { useDelegateCallback } from '../../state/governance/hooks'
import { useTokenBalance } from '../../state/wallet/hooks'
import { UNI } from '../../constants'
import { LoadingView, SubmittedView } from '../ModalViews'
import { formatTokenAmount } from 'utils/formatTokenAmount'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
@@ -116,7 +117,7 @@ export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalPro
<LoadingView onDismiss={wrappedOndismiss}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader>{usingDelegate ? 'Delegating votes' : 'Unlocking Votes'}</TYPE.largeHeader>
<TYPE.main fontSize={36}>{uniBalance?.toSignificant(4)}</TYPE.main>
<TYPE.main fontSize={36}> {formatTokenAmount(uniBalance, 4)}</TYPE.main>
</AutoColumn>
</LoadingView>
)}
@@ -124,7 +125,7 @@ export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalPro
<SubmittedView onDismiss={wrappedOndismiss} hash={hash}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader>Transaction Submitted</TYPE.largeHeader>
<TYPE.main fontSize={36}>{uniBalance?.toSignificant(4)}</TYPE.main>
<TYPE.main fontSize={36}>{formatTokenAmount(uniBalance, 4)}</TYPE.main>
</AutoColumn>
</SubmittedView>
)}

View File

@@ -12,7 +12,8 @@ import Circle from '../../assets/images/blue-loader.svg'
import { useVoteCallback, useUserVotes } from '../../state/governance/hooks'
import { getEtherscanLink } from '../../utils'
import { ExternalLink } from '../../theme/components'
import { TokenAmount } from '@uniswap/sdk-core'
import { formatTokenAmount } from 'utils/formatTokenAmount'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
@@ -48,7 +49,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, support }: Vo
}: {
voteCallback: (proposalId: string | undefined, support: boolean) => Promise<string> | undefined
} = useVoteCallback()
const availableVotes: TokenAmount | undefined = useUserVotes()
const availableVotes: CurrencyAmount<Token> | undefined = useUserVotes()
// monitor call to help UI loading state
const [hash, setHash] = useState<string | undefined>()
@@ -92,7 +93,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, support }: Vo
} proposal ${proposalId}`}</TYPE.mediumHeader>
<StyledClosed stroke="black" onClick={wrappedOndismiss} />
</RowBetween>
<TYPE.largeHeader>{availableVotes?.toSignificant(4)} Votes</TYPE.largeHeader>
<TYPE.largeHeader>{formatTokenAmount(availableVotes, 4)} Votes</TYPE.largeHeader>
<ButtonPrimary onClick={onVote}>
<TYPE.mediumHeader color="white">{`Vote ${
support ? 'for ' : 'against'

View File

@@ -39,6 +39,37 @@ export const TRIBE = new Token(ChainId.MAINNET, '0xc7283b66Eb1EB5FB86327f08e1B58
export const FRAX = new Token(ChainId.MAINNET, '0x853d955aCEf822Db058eb8505911ED77F175b99e', 18, 'FRAX', 'Frax')
export const FXS = new Token(ChainId.MAINNET, '0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0', 18, 'FXS', 'Frax Share')
export const renBTC = new Token(ChainId.MAINNET, '0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D', 8, 'renBTC', 'renBTC')
export const UMA = new Token(
ChainId.MAINNET,
'0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828',
18,
'UMA',
'UMA Voting Token v1'
)
// Mirror Protocol compat.
export const UST = new Token(ChainId.MAINNET, '0xa47c8bf37f92abed4a126bda807a7b7498661acd', 18, 'UST', 'Wrapped UST')
export const MIR = new Token(ChainId.MAINNET, '0x09a3ecafa817268f77be1283176b946c4ff2e608', 18, 'MIR', 'Wrapped MIR')
// List of all mirror's assets addresses.
// Last pulled from : https://whitelist.mirror.finance/eth/tokenlists.json
// TODO: Generate this programaticaly ?
const mAssetsAdditionalBases: { [tokenAddress: string]: Token[] } = {
[UST.address]: [MIR],
[MIR.address]: [UST],
'0xd36932143F6eBDEDD872D5Fb0651f4B72Fd15a84': [MIR, UST], // mAAPL
'0x59A921Db27Dd6d4d974745B7FfC5c33932653442': [MIR, UST], // mGOOGL
'0x21cA39943E91d704678F5D00b6616650F066fD63': [MIR, UST], // mTSLA
'0xC8d674114bac90148d11D3C1d33C61835a0F9DCD': [MIR, UST], // mNFLX
'0x13B02c8dE71680e71F0820c996E4bE43c2F57d15': [MIR, UST], // mQQQ
'0xEdb0414627E6f1e3F082DE65cD4F9C693D78CCA9': [MIR, UST], // mTWTR
'0x41BbEDd7286dAab5910a1f15d12CBda839852BD7': [MIR, UST], // mMSFT
'0x0cae9e4d663793c2a2A0b211c1Cf4bBca2B9cAa7': [MIR, UST], // mAMZN
'0x56aA298a19C93c6801FDde870fA63EF75Cc0aF72': [MIR, UST], // mBABA
'0x1d350417d9787E000cc1b95d70E9536DcD91F373': [MIR, UST], // mIAU
'0x9d1555d8cB3C846Bb4f7D5B1B1080872c3166676': [MIR, UST], // mSLV
'0x31c63146a635EB7465e5853020b39713AC356991': [MIR, UST], // mUSO
'0xf72FCd9DCF0190923Fadd44811E240Ef4533fc86': [MIR, UST], // mVIXY
}
// Block time here is slightly higher (~1s) than average in order to avoid ongoing proposals past the displayed time
export const AVERAGE_BLOCK_TIME_IN_SECS = 13
@@ -84,8 +115,10 @@ export const BASES_TO_CHECK_TRADES_AGAINST: ChainTokenList = {
export const ADDITIONAL_BASES: { [chainId in ChainId]?: { [tokenAddress: string]: Token[] } } = {
[ChainId.MAINNET]: {
...mAssetsAdditionalBases,
'0xA948E86885e12Fb09AfEF8C52142EBDbDf73cD18': [UNI[ChainId.MAINNET]],
'0x561a4717537ff4AF5c687328c0f7E90a319705C0': [UNI[ChainId.MAINNET]],
'0xa6e3454fec677772dd771788a079355e43910638': [UMA],
[FEI.address]: [TRIBE],
[TRIBE.address]: [FEI],
[FRAX.address]: [FXS],
@@ -205,10 +238,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
export const NetworkContextName = 'NETWORK'
// default allowed slippage, in bips
export const INITIAL_ALLOWED_SLIPPAGE = new Percent(10, 10_000)
// 20 minutes, denominated in seconds
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 20
// 30 minutes, denominated in seconds
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 30
// used for rewards deadlines
export const BIG_INT_SECONDS_IN_WEEK = JSBI.BigInt(60 * 60 * 24 * 7)

View File

@@ -1,16 +1,17 @@
import { TokenAddressMap, useUnsupportedTokenList } from './../state/lists/hooks'
import { parseBytes32String } from '@ethersproject/strings'
import { Currency, ETHER, Token, currencyEquals } from '@uniswap/sdk-core'
import { Currency, currencyEquals, ETHER, Token } from '@uniswap/sdk-core'
import { arrayify } from 'ethers/lib/utils'
import { useMemo } from 'react'
import { useCombinedActiveList, useCombinedInactiveList } from '../state/lists/hooks'
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'
import { useUserAddedTokens } from '../state/user/hooks'
import { isAddress } from '../utils'
import { TokenAddressMap, useUnsupportedTokenList } from './../state/lists/hooks'
import { useActiveWeb3React } from './index'
import { useBytes32TokenContract, useTokenContract } from './useContract'
import { filterTokens } from '../components/SearchModal/filtering'
import { arrayify } from 'ethers/lib/utils'
// reduce token map into standard address <-> Token mapping, optionally include user added tokens
function useTokensFromMap(tokenMap: TokenAddressMap, includeUserAdded: boolean): { [address: string]: Token } {
@@ -51,30 +52,39 @@ export function useAllTokens(): { [address: string]: Token } {
return useTokensFromMap(allTokens, true)
}
export function useAllInactiveTokens(): { [address: string]: Token } {
// get inactive tokens
const inactiveTokensMap = useCombinedInactiveList()
const inactiveTokens = useTokensFromMap(inactiveTokensMap, false)
// filter out any token that are on active list
const activeTokensAddresses = Object.keys(useAllTokens())
const filteredInactive = activeTokensAddresses
? Object.keys(inactiveTokens).reduce<{ [address: string]: Token }>((newMap, address) => {
if (!activeTokensAddresses.includes(address)) {
newMap[address] = inactiveTokens[address]
}
return newMap
}, {})
: inactiveTokens
return filteredInactive
}
export function useUnsupportedTokens(): { [address: string]: Token } {
const unsupportedTokensMap = useUnsupportedTokenList()
return useTokensFromMap(unsupportedTokensMap, false)
}
export function useSearchInactiveTokenLists(search: string | undefined, minResults = 10): WrappedTokenInfo[] {
const lists = useAllLists()
const inactiveUrls = useInactiveListUrls()
const { chainId } = useActiveWeb3React()
const activeTokens = useAllTokens()
return useMemo(() => {
if (!search || search.trim().length === 0) return []
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
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
}, [activeTokens, chainId, inactiveUrls, lists, minResults, search])
}
export function useIsTokenActive(token: Token | undefined | null): boolean {
const activeTokens = useAllTokens()
@@ -85,21 +95,6 @@ export function useIsTokenActive(token: Token | undefined | null): boolean {
return !!activeTokens[token.address]
}
// used to detect extra search results
export function useFoundOnInactiveList(searchQuery: string): Token[] | undefined {
const { chainId } = useActiveWeb3React()
const inactiveTokens = useAllInactiveTokens()
return useMemo(() => {
if (!chainId || searchQuery === '') {
return undefined
} else {
const tokens = filterTokens(Object.values(inactiveTokens), searchQuery)
return tokens
}
}, [chainId, inactiveTokens, searchQuery])
}
// Check if currency is included in custom list from user storage
export function useIsUserAddedToken(currency: Currency | undefined | null): boolean {
const userAddedTokens = useUserAddedTokens()

View File

@@ -12,16 +12,13 @@ function computeAllRoutes(
pools: Pool[],
chainId: ChainId,
currentPath: Pool[] = [],
allPaths: Route[] = [],
allPaths: Route<Currency, Currency>[] = [],
startCurrencyIn: Currency = currencyIn,
maxHops = 2
): Route[] {
): Route<Currency, Currency>[] {
const tokenIn = wrappedCurrency(currencyIn, chainId)
const tokenOut = wrappedCurrency(currencyOut, chainId)
if (!tokenIn || !tokenOut) {
throw new Error('Could not wrap currencies')
}
if (!tokenIn || !tokenOut) throw new Error('Missing tokenIn/tokenOut')
for (const pool of pools) {
if (currentPath.indexOf(pool) !== -1 || !pool.involvesToken(tokenIn)) continue
@@ -51,7 +48,10 @@ function computeAllRoutes(
* @param currencyIn the input currency
* @param currencyOut the output currency
*/
export function useAllV3Routes(currencyIn?: Currency, currencyOut?: Currency): { loading: boolean; routes: Route[] } {
export function useAllV3Routes(
currencyIn?: Currency,
currencyOut?: Currency
): { loading: boolean; routes: Route<Currency, Currency>[] } {
const { chainId } = useActiveWeb3React()
const { pools, loading: poolsLoading } = useV3SwapPools(currencyIn, currencyOut)

View File

@@ -1,6 +1,6 @@
import { MaxUint256 } from '@ethersproject/constants'
import { TransactionResponse } from '@ethersproject/providers'
import { TokenAmount, CurrencyAmount, ETHER, ChainId, Percent } from '@uniswap/sdk-core'
import { CurrencyAmount, ChainId, Percent, Currency, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { useCallback, useMemo } from 'react'
@@ -21,18 +21,18 @@ export enum ApprovalState {
// returns a variable indicating the state of the approval and a function which approves if necessary or early returns
export function useApproveCallback(
amountToApprove?: CurrencyAmount,
amountToApprove?: CurrencyAmount<Currency>,
spender?: string
): [ApprovalState, () => Promise<void>] {
const { account } = useActiveWeb3React()
const token = amountToApprove instanceof TokenAmount ? amountToApprove.token : undefined
const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined
const currentAllowance = useTokenAllowance(token, account ?? undefined, spender)
const pendingApproval = useHasPendingApproval(token?.address, spender)
// check the current approval status
const approvalState: ApprovalState = useMemo(() => {
if (!amountToApprove || !spender) return ApprovalState.UNKNOWN
if (amountToApprove.currency === ETHER) return ApprovalState.APPROVED
if (amountToApprove.currency.isEther) return ApprovalState.APPROVED
// we might not have enough data to know whether or not we need to approve
if (!currentAllowance) return ApprovalState.UNKNOWN
@@ -76,11 +76,11 @@ export function useApproveCallback(
const estimatedGas = await tokenContract.estimateGas.approve(spender, MaxUint256).catch(() => {
// general fallback for tokens who restrict approval amounts
useExact = true
return tokenContract.estimateGas.approve(spender, amountToApprove.raw.toString())
return tokenContract.estimateGas.approve(spender, amountToApprove.quotient.toString())
})
return tokenContract
.approve(spender, useExact ? amountToApprove.raw.toString() : MaxUint256, {
.approve(spender, useExact ? amountToApprove.quotient.toString() : MaxUint256, {
gasLimit: calculateGasMargin(estimatedGas),
})
.then((response: TransactionResponse) => {
@@ -99,13 +99,16 @@ export function useApproveCallback(
}
// wraps useApproveCallback in the context of a swap
export function useApproveCallbackFromTrade(trade: V2Trade | V3Trade | undefined, allowedSlippage: Percent) {
export function useApproveCallbackFromTrade(
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | undefined,
allowedSlippage: Percent
) {
const { chainId } = useActiveWeb3React()
const swapRouterAddress = SWAP_ROUTER_ADDRESSES[chainId as ChainId]
const amountToApprove = useMemo(() => (trade ? trade.maximumAmountIn(allowedSlippage) : undefined), [
trade,
allowedSlippage,
])
const amountToApprove = useMemo(
() => (trade && trade.inputAmount.currency.isToken ? trade.maximumAmountIn(allowedSlippage) : undefined),
[trade, allowedSlippage]
)
return useApproveCallback(
amountToApprove,
trade instanceof V2Trade ? V2_ROUTER_ADDRESS : trade instanceof V3Trade ? swapRouterAddress : undefined

View File

@@ -1,4 +1,4 @@
import { Token, Currency, CurrencyAmount, TokenAmount, TradeType } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { encodeRouteToPath, Route, Trade } from '@uniswap/v3-sdk'
import { BigNumber } from 'ethers'
import { useMemo } from 'react'
@@ -20,16 +20,16 @@ export enum V3TradeState {
* @param currencyOut the desired output currency
*/
export function useBestV3TradeExactIn(
amountIn?: CurrencyAmount,
amountIn?: CurrencyAmount<Currency>,
currencyOut?: Currency
): { state: V3TradeState; trade: Trade | null } {
): { state: V3TradeState; trade: Trade<Currency, Currency, TradeType.EXACT_INPUT> | null } {
const quoter = useV3Quoter()
const { routes, loading: routesLoading } = useAllV3Routes(amountIn?.currency, currencyOut)
const quoteExactInInputs = useMemo(() => {
return routes.map((route) => [
encodeRouteToPath(route, false),
amountIn ? `0x${amountIn.raw.toString(16)}` : undefined,
amountIn ? `0x${amountIn.quotient.toString(16)}` : undefined,
])
}, [amountIn, routes])
@@ -51,7 +51,7 @@ export function useBestV3TradeExactIn(
}
const { bestRoute, amountOut } = quotesResults.reduce(
(currentBest: { bestRoute: Route | null; amountOut: BigNumber | null }, { result }, i) => {
(currentBest: { bestRoute: Route<Currency, Currency> | null; amountOut: BigNumber | null }, { result }, i) => {
if (!result) return currentBest
if (currentBest.amountOut === null) {
@@ -89,10 +89,7 @@ export function useBestV3TradeExactIn(
route: bestRoute,
tradeType: TradeType.EXACT_INPUT,
inputAmount: amountIn,
outputAmount:
currencyOut instanceof Token
? new TokenAmount(currencyOut, amountOut.toString())
: CurrencyAmount.ether(amountOut.toString()),
outputAmount: CurrencyAmount.fromRawAmount(currencyOut, amountOut.toString()),
}),
}
}, [amountIn, currencyOut, quotesResults, routes, routesLoading])
@@ -105,15 +102,15 @@ export function useBestV3TradeExactIn(
*/
export function useBestV3TradeExactOut(
currencyIn?: Currency,
amountOut?: CurrencyAmount
): { state: V3TradeState; trade: Trade | null } {
amountOut?: CurrencyAmount<Currency>
): { state: V3TradeState; trade: Trade<Currency, Currency, TradeType.EXACT_OUTPUT> | null } {
const quoter = useV3Quoter()
const { routes, loading: routesLoading } = useAllV3Routes(currencyIn, amountOut?.currency)
const quoteExactOutInputs = useMemo(() => {
return routes.map((route) => [
encodeRouteToPath(route, true),
amountOut ? `0x${amountOut.raw.toString(16)}` : undefined,
amountOut ? `0x${amountOut.quotient.toString(16)}` : undefined,
])
}, [amountOut, routes])
@@ -135,7 +132,7 @@ export function useBestV3TradeExactOut(
}
const { bestRoute, amountIn } = quotesResults.reduce(
(currentBest: { bestRoute: Route | null; amountIn: BigNumber | null }, { result }, i) => {
(currentBest: { bestRoute: Route<Currency, Currency> | null; amountIn: BigNumber | null }, { result }, i) => {
if (!result) return currentBest
if (currentBest.amountIn === null) {
@@ -172,10 +169,7 @@ export function useBestV3TradeExactOut(
trade: Trade.createUncheckedTrade({
route: bestRoute,
tradeType: TradeType.EXACT_OUTPUT,
inputAmount:
currencyIn instanceof Token
? new TokenAmount(currencyIn, amountIn.toString())
: CurrencyAmount.ether(amountIn.toString()),
inputAmount: CurrencyAmount.fromRawAmount(currencyIn, amountIn.toString()),
outputAmount: amountOut,
}),
}

View File

@@ -160,10 +160,10 @@ export function useSocksController(): Unisocks | null {
) as Unisocks | null
}
export function useV3NFTPositionManagerContract(): NonfungiblePositionManager | null {
export function useV3NFTPositionManagerContract(withSignerIfPossible?: boolean): NonfungiblePositionManager | null {
const { chainId } = useActiveWeb3React()
const address = chainId ? NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId] : undefined
return useContract(address, NFTPositionManagerABI) as NonfungiblePositionManager | null
return useContract(address, NFTPositionManagerABI, withSignerIfPossible) as NonfungiblePositionManager | null
}
export function useV3Factory(): UniswapV3Factory | null {

View File

@@ -1,5 +1,5 @@
import JSBI from 'jsbi'
import { ChainId, CurrencyAmount, Percent, TokenAmount } from '@uniswap/sdk-core'
import { ChainId, Percent, CurrencyAmount, Currency, TradeType, Token } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { splitSignature } from 'ethers/lib/utils'
@@ -115,7 +115,7 @@ const PERMIT_ALLOWED_TYPE = [
]
export function useERC20Permit(
currencyAmount: CurrencyAmount | null | undefined,
currencyAmount: CurrencyAmount<Currency> | null | undefined,
spender: string | null | undefined,
overridePermitInfo: PermitInfo | undefined | null
): {
@@ -125,7 +125,7 @@ export function useERC20Permit(
} {
const { account, chainId, library } = useActiveWeb3React()
const transactionDeadline = useTransactionDeadline()
const tokenAddress = currencyAmount instanceof TokenAmount ? currencyAmount.token.address : undefined
const tokenAddress = currencyAmount?.currency?.isToken ? currencyAmount.currency.address : undefined
const eip2612Contract = useEIP2612Contract(tokenAddress)
const isArgentWallet = useIsArgentWallet()
const nonceInputs = useMemo(() => [account ?? undefined], [account])
@@ -172,7 +172,7 @@ export function useERC20Permit(
signatureData.tokenAddress === tokenAddress &&
signatureData.nonce === nonceNumber &&
signatureData.spender === spender &&
('allowed' in signatureData || JSBI.equal(JSBI.BigInt(signatureData.amount), currencyAmount.raw))
('allowed' in signatureData || JSBI.equal(JSBI.BigInt(signatureData.amount), currencyAmount.quotient))
return {
state: isSignatureDataValid ? UseERC20PermitState.SIGNED : UseERC20PermitState.NOT_SIGNED,
@@ -180,7 +180,7 @@ export function useERC20Permit(
gatherPermitSignature: async function gatherPermitSignature() {
const allowed = permitInfo.type === PermitType.ALLOWED
const signatureDeadline = transactionDeadline.toNumber() + PERMIT_VALIDITY_BUFFER
const value = currencyAmount.raw.toString()
const value = currencyAmount.quotient.toString()
const message = allowed
? {
@@ -264,13 +264,16 @@ const REMOVE_V2_LIQUIDITY_PERMIT_INFO: PermitInfo = {
}
export function useV2LiquidityTokenPermit(
liquidityAmount: TokenAmount | null | undefined,
liquidityAmount: CurrencyAmount<Token> | null | undefined,
spender: string | null | undefined
) {
return useERC20Permit(liquidityAmount, spender, REMOVE_V2_LIQUIDITY_PERMIT_INFO)
}
export function useERC20PermitFromTrade(trade: V2Trade | V3Trade | undefined, allowedSlippage: Percent) {
export function useERC20PermitFromTrade(
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | undefined,
allowedSlippage: Percent
) {
const { chainId } = useActiveWeb3React()
const swapRouterAddress = SWAP_ROUTER_ADDRESSES[chainId as ChainId]
const amountToApprove = useMemo(() => (trade ? trade.maximumAmountIn(allowedSlippage) : undefined), [

View File

@@ -14,8 +14,8 @@ export function useIsSwapUnsupported(currencyIn?: Currency, currencyOut?: Curren
// if unsupported list loaded & either token on list, mark as unsupported
return Boolean(
unsupportedTokens &&
((currencyIn && currencyIn instanceof Token && unsupportedTokens[currencyIn.address]) ||
(currencyOut && currencyOut instanceof Token && unsupportedTokens[currencyOut.address]))
((currencyIn?.isToken && unsupportedTokens[currencyIn.address]) ||
(currencyOut?.isToken && unsupportedTokens[currencyOut.address]))
)
}, [currencyIn, currencyOut, unsupportedTokens])
}

View File

@@ -1,4 +1,4 @@
import { JSBI } from '@uniswap/v2-sdk'
import JSBI from 'jsbi'
import { useMemo } from 'react'
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
import { useActiveWeb3React } from './index'

View File

@@ -1,9 +1,8 @@
import { BigNumber } from '@ethersproject/bignumber'
import { Router, Trade as V2Trade } from '@uniswap/v2-sdk'
import { SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { ChainId, Percent, TradeType } from '@uniswap/sdk-core'
import { ChainId, Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { INITIAL_ALLOWED_SLIPPAGE } from '../constants'
import { SWAP_ROUTER_ADDRESSES } from '../constants/v3'
import { getTradeVersion } from '../utils/getTradeVersion'
import { useTransactionAdder } from '../state/transactions/hooks'
@@ -50,8 +49,8 @@ interface FailedCall extends SwapCallEstimate {
* @param signatureData the signature data of the permit of the input token amount, if available
*/
function useSwapCallArguments(
trade: V2Trade | V3Trade | undefined, // trade to execute, required
allowedSlippage: Percent = INITIAL_ALLOWED_SLIPPAGE, // in bips
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | undefined, // trade to execute, required
allowedSlippage: Percent, // in bips
recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
signatureData: SignatureData | null | undefined
): SwapCall[] {
@@ -137,8 +136,8 @@ function useSwapCallArguments(
// 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(
trade: V2Trade | V3Trade | undefined, // trade to execute, required
allowedSlippage: Percent = INITIAL_ALLOWED_SLIPPAGE, // in bips
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | undefined, // trade to execute, required
allowedSlippage: Percent, // in bips
recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
signatureData: SignatureData | undefined | null
): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: string | null } {

View File

@@ -0,0 +1,20 @@
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { useMemo } from 'react'
import { useUserSlippageToleranceWithDefault } from '../state/user/hooks'
const V2_SWAP_DEFAULT_SLIPPAGE = new Percent(50, 10_000) // .50%
const V3_SWAP_DEFAULT_SLIPPAGE = new Percent(50, 10_000) // .50%
const ONE_TENTHS_PERCENT = new Percent(10, 10_000) // .10%
export default function useSwapSlippageTolerance(
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | undefined
): Percent {
const defaultSlippageTolerance = useMemo(() => {
if (!trade) return ONE_TENTHS_PERCENT
if (trade instanceof V2Trade) return V2_SWAP_DEFAULT_SLIPPAGE
return V3_SWAP_DEFAULT_SLIPPAGE
}, [trade])
return useUserSlippageToleranceWithDefault(defaultSlippageTolerance)
}

View File

@@ -5,7 +5,7 @@ export function getTickToPrice(
baseToken: Token | undefined,
quoteToken: Token | undefined,
tick: number | undefined
): Price | undefined {
): Price<Token, Token> | undefined {
if (!baseToken || !quoteToken || !tick) {
return undefined
}

View File

@@ -1,15 +1,15 @@
import { Token, TokenAmount } from '@uniswap/sdk-core'
import { Token, CurrencyAmount } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { useSingleCallResult } from '../state/multicall/hooks'
import { useTokenContract } from './useContract'
export function useTokenAllowance(token?: Token, owner?: string, spender?: string): TokenAmount | undefined {
export function useTokenAllowance(token?: Token, owner?: string, spender?: string): CurrencyAmount<Token> | undefined {
const contract = useTokenContract(token?.address, false)
const inputs = useMemo(() => [owner, spender], [owner, spender])
const allowance = useSingleCallResult(contract, 'allowance', inputs).result
return useMemo(() => (token && allowance ? new TokenAmount(token, allowance.toString()) : undefined), [
return useMemo(() => (token && allowance ? CurrencyAmount.fromRawAmount(token, allowance.toString()) : undefined), [
token,
allowance,
])

View File

@@ -1,14 +1,14 @@
import { BigNumber } from '@ethersproject/bignumber'
import { Token, TokenAmount } from '@uniswap/sdk-core'
import { Token, CurrencyAmount, Currency } from '@uniswap/sdk-core'
import { useTokenContract } from './useContract'
import { useSingleCallResult } from '../state/multicall/hooks'
// returns undefined if input token is undefined, or fails to get token contract,
// or contract total supply cannot be fetched
export function useTotalSupply(token?: Token): TokenAmount | undefined {
const contract = useTokenContract(token?.address, false)
export function useTotalSupply(token?: Currency): CurrencyAmount<Token> | undefined {
const contract = useTokenContract(token?.isToken ? token.address : undefined, false)
const totalSupply: BigNumber = useSingleCallResult(contract, 'totalSupply')?.result?.[0]
return token && totalSupply ? new TokenAmount(token, totalSupply.toString()) : undefined
return token?.isToken && totalSupply ? CurrencyAmount.fromRawAmount(token, totalSupply.toString()) : undefined
}

View File

@@ -1,5 +1,5 @@
import { ChainId, Currency, CurrencyAmount, currencyEquals, Price, Token, WETH9 } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import JSBI from 'jsbi'
import { useMemo } from 'react'
import { USDC } from '../constants'
import { PairState, useV2Pairs } from './useV2Pairs'
@@ -10,7 +10,7 @@ import { wrappedCurrency } from '../utils/wrappedCurrency'
* Returns the price in USDC of the input currency
* @param currency currency to compute the USDC price of
*/
export default function useUSDCPrice(currency?: Currency): Price | undefined {
export default function useUSDCPrice(currency?: Currency): Price<Currency, Token> | undefined {
const { chainId } = useActiveWeb3React()
const wrapped = wrappedCurrency(currency, chainId)
const weth = WETH9[chainId as ChainId]
@@ -56,7 +56,7 @@ export default function useUSDCPrice(currency?: Currency): Price | undefined {
const ethPairETHAmount = ethPair?.reserveOf(weth)
const ethPairETHUSDCValue: JSBI =
ethPairETHAmount && usdcEthPair ? usdcEthPair.priceOf(weth).quote(ethPairETHAmount).raw : JSBI.BigInt(0)
ethPairETHAmount && usdcEthPair ? usdcEthPair.priceOf(weth).quote(ethPairETHAmount).quotient : JSBI.BigInt(0)
// all other tokens
// first try the usdc pair
@@ -76,7 +76,7 @@ export default function useUSDCPrice(currency?: Currency): Price | undefined {
}, [chainId, currency, ethPair, ethPairState, usdcEthPair, usdcEthPairState, usdcPair, usdcPairState, weth, wrapped])
}
export function useUSDCValue(currencyAmount: CurrencyAmount | undefined | null) {
export function useUSDCValue(currencyAmount: CurrencyAmount<Currency> | undefined | null) {
const price = useUSDCPrice(currencyAmount?.currency)
return useMemo(() => {

View File

@@ -5,7 +5,7 @@ import { Interface } from '@ethersproject/abi'
import { useActiveWeb3React } from './index'
import { useMultipleContractSingleData } from '../state/multicall/hooks'
import { wrappedCurrency } from '../utils/wrappedCurrency'
import { Currency, TokenAmount } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)
@@ -51,7 +51,10 @@ export function useV2Pairs(currencies: [Currency | undefined, Currency | undefin
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
return [
PairState.EXISTS,
new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString())),
new Pair(
CurrencyAmount.fromRawAmount(token0, reserve0.toString()),
CurrencyAmount.fromRawAmount(token1, reserve1.toString())
),
]
})
}, [results, tokens])

View File

@@ -1,4 +1,4 @@
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { Pair, Trade } from '@uniswap/v2-sdk'
import { useMemo } from 'react'
import { useUserSingleHopOnly } from 'state/user/hooks'
@@ -34,7 +34,10 @@ const MAX_HOPS = 3
/**
* Returns the best trade for the exact amount of tokens in to the given token out
*/
export function useV2TradeExactIn(currencyAmountIn?: CurrencyAmount, currencyOut?: Currency): Trade | null {
export function useV2TradeExactIn(
currencyAmountIn?: CurrencyAmount<Currency>,
currencyOut?: Currency
): Trade<Currency, Currency, TradeType.EXACT_INPUT> | null {
const allowedPairs = useAllCommonPairs(currencyAmountIn?.currency, currencyOut)
const [singleHopOnly] = useUserSingleHopOnly()
@@ -48,9 +51,9 @@ export function useV2TradeExactIn(currencyAmountIn?: CurrencyAmount, currencyOut
)
}
// search through trades with varying hops, find best trade out of them
let bestTradeSoFar: Trade | null = null
let bestTradeSoFar: Trade<Currency, Currency, TradeType.EXACT_INPUT> | null = null
for (let i = 1; i <= MAX_HOPS; i++) {
const currentTrade: Trade | null =
const currentTrade: Trade<Currency, Currency, TradeType.EXACT_INPUT> | null =
Trade.bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops: i, maxNumResults: 1 })[0] ??
null
// if current trade is best yet, save it
@@ -68,7 +71,10 @@ export function useV2TradeExactIn(currencyAmountIn?: CurrencyAmount, currencyOut
/**
* Returns the best trade for the token in to the exact amount of token out
*/
export function useV2TradeExactOut(currencyIn?: Currency, currencyAmountOut?: CurrencyAmount): Trade | null {
export function useV2TradeExactOut(
currencyIn?: Currency,
currencyAmountOut?: CurrencyAmount<Currency>
): Trade<Currency, Currency, TradeType.EXACT_OUTPUT> | null {
const allowedPairs = useAllCommonPairs(currencyIn, currencyAmountOut?.currency)
const [singleHopOnly] = useUserSingleHopOnly()
@@ -82,7 +88,7 @@ export function useV2TradeExactOut(currencyIn?: Currency, currencyAmountOut?: Cu
)
}
// search through trades with varying hops, find best trade out of them
let bestTradeSoFar: Trade | null = null
let bestTradeSoFar: Trade<Currency, Currency, TradeType.EXACT_OUTPUT> | null = null
for (let i = 1; i <= MAX_HOPS; i++) {
const currentTrade =
Trade.bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops: i, maxNumResults: 1 })[0] ??

View File

@@ -1,126 +1,50 @@
import { useSingleCallResult } from 'state/multicall/hooks'
import { useMemo } from 'react'
import { PositionDetails } from 'types/position'
import { useV3Pool } from './useContract'
import { useEffect, useState } from 'react'
import { useV3NFTPositionManagerContract } from './useContract'
import { BigNumber } from '@ethersproject/bignumber'
import { computePoolAddress, Pool } from '@uniswap/v3-sdk'
import { V3_CORE_FACTORY_ADDRESSES } from 'constants/v3'
import { useActiveWeb3React } from 'hooks'
import { TokenAmount } from '@uniswap/sdk-core'
import { Pool } from '@uniswap/v3-sdk'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useBlockNumber } from 'state/application/hooks'
// TODO port these utility functions to the SDK
function subIn256(x: BigNumber, y: BigNumber): BigNumber {
const difference = x.sub(y)
return difference.lt(0) ? BigNumber.from(2).pow(256).add(difference) : difference
}
function getCounterfactualFees(
feeGrowthGlobal: BigNumber,
feeGrowthOutsideLower: BigNumber,
feeGrowthOutsideUpper: BigNumber,
feeGrowthInsideLast: BigNumber,
pool: Pool,
liquidity: BigNumber,
tickLower: number,
tickUpper: number
) {
let feeGrowthBelow: BigNumber
if (pool.tickCurrent >= tickLower) {
feeGrowthBelow = feeGrowthOutsideLower
} else {
feeGrowthBelow = subIn256(feeGrowthGlobal, feeGrowthOutsideLower)
}
let feeGrowthAbove: BigNumber
if (pool.tickCurrent < tickUpper) {
feeGrowthAbove = feeGrowthOutsideUpper
} else {
feeGrowthAbove = subIn256(feeGrowthGlobal, feeGrowthOutsideUpper)
}
const feeGrowthInside = subIn256(subIn256(feeGrowthGlobal, feeGrowthBelow), feeGrowthAbove)
return subIn256(feeGrowthInside, feeGrowthInsideLast).mul(liquidity).div(BigNumber.from(2).pow(128))
}
const MAX_UINT128 = BigNumber.from(2).pow(128).sub(1)
// compute current + counterfactual fees for a v3 position
export function useV3PositionFees(
pool?: Pool,
positionDetails?: PositionDetails
): [TokenAmount, TokenAmount] | [undefined, undefined] {
const { chainId } = useActiveWeb3React()
tokenId?: BigNumber
): [CurrencyAmount<Token>, CurrencyAmount<Token>] | [undefined, undefined] {
const positionManager = useV3NFTPositionManagerContract(false)
const owner = useSingleCallResult(tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0]
const poolAddress = useMemo(() => {
try {
return chainId && V3_CORE_FACTORY_ADDRESSES[chainId] && pool && positionDetails
? computePoolAddress({
factoryAddress: V3_CORE_FACTORY_ADDRESSES[chainId] as string,
tokenA: pool.token0,
tokenB: pool.token1,
fee: positionDetails.fee,
})
: undefined
} catch {
return undefined
const tokenIdHexString = tokenId?.toHexString()
const latestBlockNumber = useBlockNumber()
// TODO find a way to get this into multicall
// because these amounts don't ever go down, we don't actually need to clear this state
// latestBlockNumber is included to ensure data stays up-to-date every block
const [amounts, setAmounts] = useState<[BigNumber, BigNumber]>()
useEffect(() => {
if (positionManager && tokenIdHexString && owner && typeof latestBlockNumber === 'number') {
positionManager.callStatic
.collect(
{
tokenId: tokenIdHexString,
recipient: owner, // some tokens might fail if transferred to address(0)
amount0Max: MAX_UINT128,
amount1Max: MAX_UINT128,
},
{ from: owner } // need to simulate the call as the owner
)
.then((results) => {
setAmounts([results.amount0, results.amount1])
})
}
}, [chainId, pool, positionDetails])
const poolContract = useV3Pool(poolAddress)
}, [positionManager, tokenIdHexString, owner, latestBlockNumber])
// data fetching
const feeGrowthGlobal0: BigNumber | undefined = useSingleCallResult(poolContract, 'feeGrowthGlobal0X128')?.result?.[0]
const feeGrowthGlobal1: BigNumber | undefined = useSingleCallResult(poolContract, 'feeGrowthGlobal1X128')?.result?.[0]
const { feeGrowthOutside0X128: feeGrowthOutsideLower0 } = (useSingleCallResult(poolContract, 'ticks', [
positionDetails?.tickLower,
])?.result ?? {}) as { feeGrowthOutside0X128?: BigNumber }
const { feeGrowthOutside1X128: feeGrowthOutsideLower1 } = (useSingleCallResult(poolContract, 'ticks', [
positionDetails?.tickLower,
])?.result ?? {}) as { feeGrowthOutside1X128?: BigNumber }
const { feeGrowthOutside0X128: feeGrowthOutsideUpper0 } = (useSingleCallResult(poolContract, 'ticks', [
positionDetails?.tickUpper,
])?.result ?? {}) as { feeGrowthOutside0X128?: BigNumber }
const { feeGrowthOutside1X128: feeGrowthOutsideUpper1 } = (useSingleCallResult(poolContract, 'ticks', [
positionDetails?.tickUpper,
])?.result ?? {}) as { feeGrowthOutside1X128?: BigNumber }
// calculate fees
const counterfactualFees0 =
positionDetails && pool && feeGrowthGlobal0 && feeGrowthOutsideLower0 && feeGrowthOutsideUpper0
? getCounterfactualFees(
feeGrowthGlobal0,
feeGrowthOutsideLower0,
feeGrowthOutsideUpper0,
positionDetails.feeGrowthInside0LastX128,
pool,
positionDetails.liquidity,
positionDetails.tickLower,
positionDetails.tickUpper
)
: undefined
const counterfactualFees1 =
positionDetails && pool && feeGrowthGlobal1 && feeGrowthOutsideLower1 && feeGrowthOutsideUpper1
? getCounterfactualFees(
feeGrowthGlobal1,
feeGrowthOutsideLower1,
feeGrowthOutsideUpper1,
positionDetails.feeGrowthInside1LastX128,
pool,
positionDetails.liquidity,
positionDetails.tickLower,
positionDetails.tickUpper
)
: undefined
if (
pool &&
positionDetails?.tokensOwed0 &&
positionDetails?.tokensOwed1 &&
counterfactualFees0 &&
counterfactualFees1
) {
if (pool && amounts) {
return [
new TokenAmount(pool.token0, positionDetails.tokensOwed0.add(counterfactualFees0).toString()),
new TokenAmount(pool.token1, positionDetails.tokensOwed1.add(counterfactualFees1).toString()),
CurrencyAmount.fromRawAmount(pool.token0, amounts[0].toString()),
CurrencyAmount.fromRawAmount(pool.token1, amounts[1].toString()),
]
} else {
return [undefined, undefined]

View File

@@ -1,4 +1,4 @@
import { Currency, currencyEquals, ETHER, WETH9 } from '@uniswap/sdk-core'
import { Currency, currencyEquals, WETH9 } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { tryParseAmount } from '../state/swap/hooks'
import { useTransactionAdder } from '../state/transactions/hooks'
@@ -37,14 +37,14 @@ export default function useWrapCallback(
const hasInputAmount = Boolean(inputAmount?.greaterThan('0'))
const sufficientBalance = inputAmount && balance && !balance.lessThan(inputAmount)
if (inputCurrency === ETHER && currencyEquals(WETH9[chainId], outputCurrency)) {
if (inputCurrency.isEther && currencyEquals(WETH9[chainId], outputCurrency)) {
return {
wrapType: WrapType.WRAP,
execute:
sufficientBalance && inputAmount
? async () => {
try {
const txReceipt = await wethContract.deposit({ value: `0x${inputAmount.raw.toString(16)}` })
const txReceipt = await wethContract.deposit({ value: `0x${inputAmount.quotient.toString(16)}` })
addTransaction(txReceipt, { summary: `Wrap ${inputAmount.toSignificant(6)} ETH to WETH` })
} catch (error) {
console.error('Could not deposit', error)
@@ -53,14 +53,14 @@ export default function useWrapCallback(
: undefined,
inputError: sufficientBalance ? undefined : hasInputAmount ? 'Insufficient ETH balance' : 'Enter ETH amount',
}
} else if (currencyEquals(WETH9[chainId], inputCurrency) && outputCurrency === ETHER) {
} else if (currencyEquals(WETH9[chainId], inputCurrency) && outputCurrency.isEther) {
return {
wrapType: WrapType.UNWRAP,
execute:
sufficientBalance && inputAmount
? async () => {
try {
const txReceipt = await wethContract.withdraw(`0x${inputAmount.raw.toString(16)}`)
const txReceipt = await wethContract.withdraw(`0x${inputAmount.quotient.toString(16)}`)
addTransaction(txReceipt, { summary: `Unwrap ${inputAmount.toSignificant(6)} WETH to ETH` })
} catch (error) {
console.error('Could not withdraw', error)

View File

@@ -46,13 +46,6 @@ if (typeof GOOGLE_ANALYTICS_ID === 'string') {
ReactGA.initialize('test', { testMode: true, debug: true })
}
window.addEventListener('error', (error) => {
ReactGA.exception({
description: `${error.message} @ ${error.filename}:${error.lineno}:${error.colno}`,
fatal: true,
})
})
function Updaters() {
return (
<>

View File

@@ -17,7 +17,7 @@ export function PoolPriceBar({
currencies: { [field in Field]?: Currency }
noLiquidity?: boolean
poolTokenPercentage?: Percent
price?: Price
price?: Price<Currency, Currency>
}) {
const theme = useContext(ThemeContext)
return (

View File

@@ -3,7 +3,7 @@ import { Field } from '../../state/mint/v3/actions'
import { AutoColumn } from 'components/Column'
import Card from 'components/Card'
import styled from 'styled-components'
import { CurrencyAmount, Price } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Price } from '@uniswap/sdk-core'
import { Position } from '@uniswap/v3-sdk'
import { PositionPreview } from 'components/PositionPreview'
@@ -26,9 +26,9 @@ export function Review({
}: {
position?: Position
existingPosition?: Position
parsedAmounts: { [field in Field]?: CurrencyAmount }
priceLower?: Price
priceUpper?: Price
parsedAmounts: { [field in Field]?: CurrencyAmount<Currency> }
priceLower?: Price<Currency, Currency>
priceUpper?: Price<Currency, Currency>
outOfRange: boolean
}) {
return (

View File

@@ -1,9 +1,10 @@
import React, { useCallback, useContext, useMemo, useState, useEffect } from 'react'
import { TransactionResponse } from '@ethersproject/providers'
import { Currency, TokenAmount, ETHER, currencyEquals } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, currencyEquals, Percent } from '@uniswap/sdk-core'
import { WETH9 } from '@uniswap/sdk-core'
import { AlertTriangle, AlertCircle } from 'react-feather'
import ReactGA from 'react-ga'
import { ZERO_PERCENT } from '../../constants'
import { useV3NFTPositionManagerContract } from '../../hooks/useContract'
import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
@@ -13,7 +14,7 @@ import { YellowCard, OutlineCard, BlueCard, LightCard } from '../../components/C
import { AutoColumn } from '../../components/Column'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { RowBetween } from '../../components/Row'
import { RowBetween, RowFixed } from '../../components/Row'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import { useUSDCValue } from '../../hooks/useUSDCPrice'
import Review from './Review'
@@ -25,7 +26,7 @@ import { useWalletModalToggle } from '../../state/application/hooks'
import { Field, Bound } from '../../state/mint/v3/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageTolerance } from '../../state/user/hooks'
import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { TYPE, ExternalLink } from '../../theme'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import AppBody from '../AppBody'
@@ -51,6 +52,9 @@ import RateToggle from 'components/RateToggle'
import { BigNumber } from '@ethersproject/bignumber'
import { calculateGasMargin } from 'utils'
import { AddRemoveTabs } from 'components/NavigationTabs'
import HoverInlineText from 'components/HoverInlineText'
const DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
export default function AddLiquidity({
match: {
@@ -148,7 +152,7 @@ export default function AddLiquidity({
// txn values
const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
@@ -163,7 +167,7 @@ export default function AddLiquidity({
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
const maxAmounts: { [field in Field]?: CurrencyAmount<Currency> } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
@@ -173,7 +177,7 @@ export default function AddLiquidity({
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
const atMaxAmounts: { [field in Field]?: CurrencyAmount<Currency> } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
@@ -193,6 +197,10 @@ export default function AddLiquidity({
chainId ? NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId] : undefined
)
const allowedSlippage = useUserSlippageToleranceWithDefault(
outOfRange ? ZERO_PERCENT : DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE
)
async function onAdd() {
if (!chainId || !library || !account) return
@@ -207,13 +215,13 @@ export default function AddLiquidity({
tokenId,
slippageTolerance: allowedSlippage,
deadline: deadline.toString(),
useEther: currencyA === ETHER || currencyB === ETHER,
useEther: currencyA.isEther || currencyB.isEther,
})
: NonfungiblePositionManager.addCallParameters(position, {
slippageTolerance: allowedSlippage,
recipient: account,
deadline: deadline.toString(),
useEther: currencyA === ETHER || currencyB === ETHER,
useEther: currencyA.isEther || currencyB.isEther,
createPool: noLiquidity,
})
@@ -396,7 +404,12 @@ export default function AddLiquidity({
pendingText={pendingText}
/>
<AppBody>
<AddRemoveTabs creating={false} adding={true} positionID={tokenId} />
<AddRemoveTabs
creating={false}
adding={true}
positionID={tokenId}
defaultSlippage={DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE}
/>
<Wrapper>
<AutoColumn gap="32px">
{!hasExistingPosition && (
@@ -486,8 +499,13 @@ export default function AddLiquidity({
<TYPE.main>
{price ? (
<TYPE.main>
{invertPrice ? price?.invert()?.toSignificant(8) : price?.toSignificant(8)}{' '}
{quoteCurrency?.symbol}
<RowFixed>
<HoverInlineText
maxCharacters={20}
text={invertPrice ? price?.invert()?.toSignificant(5) : price?.toSignificant(5)}
/>{' '}
<span style={{ marginLeft: '4px' }}>{quoteCurrency?.symbol}</span>
</RowFixed>
</TYPE.main>
) : (
'-'
@@ -540,7 +558,7 @@ export default function AddLiquidity({
<TYPE.main fontSize={14} fontWeight={400} style={{ marginBottom: '.5rem', lineHeight: '125%' }}>
Your liquidity will only earn fees when the market price of the pair is within your range.{' '}
<ExternalLink
href={'https://docs.uniswap.org/concepts/introduction/liquidity-user-guide'}
href={'https://docs.uniswap.org/concepts/introduction/liquidity-user-guide#4-set-price-range'}
style={{ fontSize: '14px' }}
>
Need help picking a range?
@@ -568,10 +586,13 @@ export default function AddLiquidity({
Current Price
</TYPE.main>
<TYPE.body fontWeight={500} textAlign="center" fontSize={20}>
{invertPrice ? price.invert().toSignificant(3) : price.toSignificant(3)}{' '}
<HoverInlineText
maxCharacters={20}
text={invertPrice ? price.invert().toSignificant(5) : price.toSignificant(5)}
/>{' '}
</TYPE.body>
<TYPE.main fontWeight={500} textAlign="center" fontSize={12}>
{quoteCurrency?.symbol} {' / '}
{quoteCurrency?.symbol} {' per '}
{baseCurrency.symbol}
</TYPE.main>
</AutoColumn>

View File

@@ -18,7 +18,7 @@ export function ConfirmAddModalBottom({
noLiquidity?: boolean
price?: Fraction
currencies: { [field in Field]?: Currency }
parsedAmounts: { [field in Field]?: CurrencyAmount }
parsedAmounts: { [field in Field]?: CurrencyAmount<Currency> }
poolTokenPercentage?: Percent
onAdd: () => void
}) {

View File

@@ -17,7 +17,7 @@ export function PoolPriceBar({
currencies: { [field in Field]?: Currency }
noLiquidity?: boolean
poolTokenPercentage?: Percent
price?: Price
price?: Price<Currency, Currency>
}) {
const theme = useContext(ThemeContext)
return (

View File

@@ -1,6 +1,6 @@
import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse } from '@ethersproject/providers'
import { Currency, currencyEquals, ETHER, TokenAmount, WETH9 } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, currencyEquals, Percent, WETH9 } from '@uniswap/sdk-core'
import React, { useCallback, useContext, useState } from 'react'
import { Plus } from 'react-feather'
import ReactGA from 'react-ga'
@@ -30,7 +30,7 @@ import { Field } from '../../state/mint/actions'
import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/hooks'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageTolerance } from '../../state/user/hooks'
import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { TYPE } from '../../theme'
import { calculateGasMargin, calculateSlippageAmount } from '../../utils'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
@@ -42,6 +42,8 @@ import { currencyId } from '../../utils/currencyId'
import { PoolPriceBar } from './PoolPriceBar'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
const DEFAULT_ADD_V2_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
@@ -90,7 +92,7 @@ export default function AddLiquidity({
// txn values
const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_ADD_V2_SLIPPAGE_TOLERANCE) // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
@@ -100,7 +102,7 @@ export default function AddLiquidity({
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
const maxAmounts: { [field in Field]?: CurrencyAmount<Currency> } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
@@ -110,7 +112,7 @@ export default function AddLiquidity({
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
const atMaxAmounts: { [field in Field]?: CurrencyAmount<Currency> } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
@@ -145,27 +147,27 @@ export default function AddLiquidity({
method: (...args: any) => Promise<TransactionResponse>,
args: Array<string | string[] | number>,
value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsETH = currencyB === ETHER
if (currencyA.isEther || currencyB.isEther) {
const tokenBIsETH = currencyB.isEther
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsETH ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
(tokenBIsETH ? parsedAmountA : parsedAmountB).quotient.toString(), // token desired
amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadline.toHexString(),
]
value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString())
value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).quotient.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
parsedAmountA.quotient.toString(),
parsedAmountB.quotient.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
@@ -315,7 +317,7 @@ export default function AddLiquidity({
return (
<>
<AppBody>
<AddRemoveTabs creating={isCreate} adding={true} />
<AddRemoveTabs creating={isCreate} adding={true} defaultSlippage={DEFAULT_ADD_V2_SLIPPAGE_TOLERANCE} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}

View File

@@ -5,9 +5,9 @@ import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsRepo
import AddressClaimModal from '../components/claim/AddressClaimModal'
import Header from '../components/Header'
import Polling from '../components/Header/Polling'
// import URLWarning from '../components/Header/URLWarning'
import Popups from '../components/Popups'
import Web3ReactManager from '../components/Web3ReactManager'
import ErrorBoundary from '../components/ErrorBoundary'
import { ApplicationModal } from '../state/application/actions'
import { useModalOpen, useToggleModal } from '../state/application/hooks'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
@@ -72,62 +72,69 @@ function TopLevelModals() {
export default function App() {
return (
<Suspense fallback={null}>
<Route component={GoogleAnalyticsReporter} />
<Route component={DarkModeQueryParamReader} />
<AppWrapper>
<HeaderWrapper>
<Header />
</HeaderWrapper>
<BodyWrapper>
<ThemedBackground />
<Popups />
<Polling />
<TopLevelModals />
<Web3ReactManager>
<Switch>
<Route exact strict path="/vote" component={Vote} />
<Route exact strict path="/vote/:id" component={VotePage} />
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} />
<Route exact strict path="/uni" component={Earn} />
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
<ErrorBoundary>
<Suspense fallback={null}>
<Route component={GoogleAnalyticsReporter} />
<Route component={DarkModeQueryParamReader} />
<AppWrapper>
<HeaderWrapper>
<Header />
</HeaderWrapper>
<BodyWrapper>
<ThemedBackground />
<Popups />
<Polling />
<TopLevelModals />
<Web3ReactManager>
<Switch>
<Route exact strict path="/vote" component={Vote} />
<Route exact strict path="/vote/:id" component={VotePage} />
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} />
<Route exact strict path="/uni" component={Earn} />
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
<Route exact strict path="/send" component={RedirectPathToSwapOnly} />
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
<Route exact strict path="/swap" component={Swap} />
<Route exact strict path="/send" component={RedirectPathToSwapOnly} />
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
<Route exact strict path="/swap" component={Swap} />
<Route exact strict path="/find" component={PoolFinder} />
<Route exact strict path="/pool/v2" component={PoolV2} />
<Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/pool/:tokenId" component={PositionPage} />
<Route exact strict path="/find" component={PoolFinder} />
<Route exact strict path="/pool/v2" component={PoolV2} />
<Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/pool/:tokenId" component={PositionPage} />
<Route exact strict path="/add/v2/:currencyIdA?/:currencyIdB?" component={RedirectDuplicateTokenIdsV2} />
<Route
exact
strict
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?"
component={RedirectDuplicateTokenIds}
/>
<Route
exact
strict
path="/add/v2/:currencyIdA?/:currencyIdB?"
component={RedirectDuplicateTokenIdsV2}
/>
<Route
exact
strict
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?"
component={RedirectDuplicateTokenIds}
/>
<Route
exact
strict
path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?"
component={AddLiquidity}
/>
<Route
exact
strict
path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?"
component={AddLiquidity}
/>
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
<Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} />
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
<Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} />
<Route exact strict path="/migrate/v2" component={MigrateV2} />
<Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} />
<Route exact strict path="/migrate/v2" component={MigrateV2} />
<Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} />
<Route component={RedirectPathToSwapOnly} />
</Switch>
</Web3ReactManager>
<Marginer />
</BodyWrapper>
</AppWrapper>
</Suspense>
<Route component={RedirectPathToSwapOnly} />
</Switch>
</Web3ReactManager>
<Marginer />
</BodyWrapper>
</AppWrapper>
</Suspense>
</ErrorBoundary>
)
}

View File

@@ -2,9 +2,8 @@ import React, { useCallback, useState } from 'react'
import { AutoColumn } from '../../components/Column'
import styled from 'styled-components'
import { Link } from 'react-router-dom'
import { TokenAmount, ETHER } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import JSBI from 'jsbi'
import { Token, CurrencyAmount } from '@uniswap/sdk-core'
import { RouteComponentProps } from 'react-router-dom'
import DoubleCurrencyLogo from '../../components/DoubleLogo'
import { useCurrency } from '../../hooks/Tokens'
@@ -104,7 +103,7 @@ export default function Manage({
const stakingInfo = useStakingInfo(stakingTokenPair)?.[0]
// detect existing unstaked LP position to show add button if none found
const userLiquidityUnstaked = useTokenBalance(account ?? undefined, stakingInfo?.stakedAmount?.token)
const userLiquidityUnstaked = useTokenBalance(account ?? undefined, stakingInfo?.stakedAmount?.currency)
const showAddLiquidityButton = Boolean(stakingInfo?.stakedAmount?.equalTo('0') && userLiquidityUnstaked?.equalTo('0'))
// toggle for staking modal and unstaking modal
@@ -115,23 +114,23 @@ export default function Manage({
// fade cards if nothing staked or nothing earned yet
const disableTop = !stakingInfo?.stakedAmount || stakingInfo.stakedAmount.equalTo(JSBI.BigInt(0))
const token = currencyA === ETHER ? tokenB : tokenA
const WETH = currencyA === ETHER ? tokenA : tokenB
const token = currencyA?.isEther ? tokenB : tokenA
const WETH = currencyA?.isEther ? tokenA : tokenB
const backgroundColor = useColor(token)
// get WETH value of staked LP tokens
const totalSupplyOfStakingToken = useTotalSupply(stakingInfo?.stakedAmount?.token)
let valueOfTotalStakedAmountInWETH: TokenAmount | undefined
const totalSupplyOfStakingToken = useTotalSupply(stakingInfo?.stakedAmount?.currency)
let valueOfTotalStakedAmountInWETH: CurrencyAmount<Token> | undefined
if (totalSupplyOfStakingToken && stakingTokenPair && stakingInfo && WETH) {
// take the total amount of LP tokens staked, multiply by ETH value of all LP tokens, divide by all LP tokens
valueOfTotalStakedAmountInWETH = new TokenAmount(
valueOfTotalStakedAmountInWETH = CurrencyAmount.fromRawAmount(
WETH,
JSBI.divide(
JSBI.multiply(
JSBI.multiply(stakingInfo.totalStakedAmount.raw, stakingTokenPair.reserveOf(WETH).raw),
JSBI.multiply(stakingInfo.totalStakedAmount.quotient, stakingTokenPair.reserveOf(WETH).quotient),
JSBI.BigInt(2) // this is b/c the value of LP shares are ~double the value of the WETH they entitle owner to
),
totalSupplyOfStakingToken.raw
totalSupplyOfStakingToken.quotient
)
)
}
@@ -269,7 +268,7 @@ export default function Manage({
<div>
<TYPE.black>Your unclaimed UNI</TYPE.black>
</div>
{stakingInfo?.earnedAmount && JSBI.notEqual(BIG_INT_ZERO, stakingInfo?.earnedAmount?.raw) && (
{stakingInfo?.earnedAmount && JSBI.notEqual(BIG_INT_ZERO, stakingInfo?.earnedAmount?.quotient) && (
<ButtonEmpty
padding="8px"
borderRadius="8px"

View File

@@ -1,3 +1,4 @@
import JSBI from 'jsbi'
import React from 'react'
import { AutoColumn } from '../../components/Column'
import styled from 'styled-components'
@@ -9,7 +10,6 @@ import { CardSection, DataCard, CardNoise, CardBGImage } from '../../components/
import { Countdown } from './Countdown'
import Loader from '../../components/Loader'
import { useActiveWeb3React } from '../../hooks'
import { JSBI } from '@uniswap/v2-sdk'
import { BIG_INT_ZERO } from '../../constants'
import { OutlineCard } from '../../components/Card'
@@ -48,7 +48,7 @@ export default function Earn() {
* only show staking cards with balance
* @todo only account for this if rewards are inactive
*/
const stakingInfosWithBalance = stakingInfos?.filter((s) => JSBI.greaterThan(s.stakedAmount.raw, BIG_INT_ZERO))
const stakingInfosWithBalance = stakingInfos?.filter((s) => JSBI.greaterThan(s.stakedAmount.quotient, BIG_INT_ZERO))
// toggle copy if rewards are inactive
const stakingRewardsExist = Boolean(typeof chainId === 'number' && (STAKING_REWARDS_INFO[chainId]?.length ?? 0) > 0)

View File

@@ -1,6 +1,7 @@
import JSBI from 'jsbi'
import React, { useCallback, useMemo, useState, useEffect } from 'react'
import { Fraction, Price, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
import { FACTORY_ADDRESS, JSBI } from '@uniswap/v2-sdk'
import { Fraction, Percent, Price, Token, CurrencyAmount, WETH9 } from '@uniswap/sdk-core'
import { FACTORY_ADDRESS } from '@uniswap/v2-sdk'
import { Redirect, RouteComponentProps } from 'react-router'
import { Text } from 'rebass'
import { AutoColumn } from '../../components/Column'
@@ -25,7 +26,7 @@ import { ApprovalState, useApproveCallback } from 'hooks/useApproveCallback'
import { Dots } from 'components/swap/styleds'
import { ButtonConfirmed } from 'components/Button'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import { useUserSlippageTolerance } from 'state/user/hooks'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import ReactGA from 'react-ga'
import { TransactionResponse } from '@ethersproject/providers'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
@@ -49,6 +50,8 @@ import SettingsTab from 'components/Settings'
const ZERO = JSBI.BigInt(0)
const DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE = new Percent(75, 10_000)
function EmptyState({ message }: { message: string }) {
return (
<AutoColumn style={{ minHeight: 200, justifyContent: 'center', alignItems: 'center' }}>
@@ -57,9 +60,15 @@ function EmptyState({ message }: { message: string }) {
)
}
function LiquidityInfo({ token0Amount, token1Amount }: { token0Amount: TokenAmount; token1Amount: TokenAmount }) {
const currency0 = unwrappedToken(token0Amount.token)
const currency1 = unwrappedToken(token1Amount.token)
function LiquidityInfo({
token0Amount,
token1Amount,
}: {
token0Amount: CurrencyAmount<Token>
token1Amount: CurrencyAmount<Token>
}) {
const currency0 = unwrappedToken(token0Amount.currency)
const currency1 = unwrappedToken(token1Amount.currency)
return (
<AutoColumn gap="8px">
@@ -103,10 +112,10 @@ function V2PairMigration({
token1,
}: {
pair: Contract
pairBalance: TokenAmount
totalSupply: TokenAmount
reserve0: TokenAmount
reserve1: TokenAmount
pairBalance: CurrencyAmount<Token>
totalSupply: CurrencyAmount<Token>
reserve0: CurrencyAmount<Token>
reserve1: CurrencyAmount<Token>
token0: Token
token1: Token
}) {
@@ -119,18 +128,26 @@ function V2PairMigration({
const deadline = useTransactionDeadline() // custom from users settings
const blockTimestamp = useCurrentBlockTimestamp()
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE) // custom from users
const currency0 = unwrappedToken(token0)
const currency1 = unwrappedToken(token1)
// this is just getLiquidityValue with the fee off, but for the passed pair
const token0Value = useMemo(
() => new TokenAmount(token0, JSBI.divide(JSBI.multiply(pairBalance.raw, reserve0.raw), totalSupply.raw)),
() =>
CurrencyAmount.fromRawAmount(
token0,
JSBI.divide(JSBI.multiply(pairBalance.quotient, reserve0.quotient), totalSupply.quotient)
),
[token0, pairBalance, reserve0, totalSupply]
)
const token1Value = useMemo(
() => new TokenAmount(token1, JSBI.divide(JSBI.multiply(pairBalance.raw, reserve1.raw), totalSupply.raw)),
() =>
CurrencyAmount.fromRawAmount(
token1,
JSBI.divide(JSBI.multiply(pairBalance.quotient, reserve1.quotient), totalSupply.quotient)
),
[token1, pairBalance, reserve1, totalSupply]
)
@@ -140,7 +157,7 @@ function V2PairMigration({
const noLiquidity = poolState === PoolState.NOT_EXISTS
// get spot prices + price difference
const v2SpotPrice = useMemo(() => new Price(token0, token1, reserve0.raw, reserve1.raw), [
const v2SpotPrice = useMemo(() => new Price(token0, token1, reserve0.quotient, reserve1.quotient), [
token0,
token1,
reserve0,
@@ -189,18 +206,18 @@ function V2PairMigration({
pool: pool ?? new Pool(token0, token1, feeAmount, sqrtPrice, 0, tick, []),
tickLower,
tickUpper,
amount0: token0Value.raw,
amount1: token1Value.raw,
amount0: token0Value.quotient,
amount1: token1Value.quotient,
})
: undefined
const v3Amount0Min = useMemo(
() =>
position &&
new TokenAmount(
CurrencyAmount.fromRawAmount(
token0,
JSBI.divide(
JSBI.multiply(position.amount0.raw, JSBI.BigInt(10000 - JSBI.toNumber(allowedSlippage.numerator))),
JSBI.multiply(position.amount0.quotient, JSBI.BigInt(10000 - JSBI.toNumber(allowedSlippage.numerator))),
JSBI.BigInt(10000)
)
),
@@ -209,10 +226,10 @@ function V2PairMigration({
const v3Amount1Min = useMemo(
() =>
position &&
new TokenAmount(
CurrencyAmount.fromRawAmount(
token1,
JSBI.divide(
JSBI.multiply(position.amount1.raw, JSBI.BigInt(10000 - JSBI.toNumber(allowedSlippage.numerator))),
JSBI.multiply(position.amount1.quotient, JSBI.BigInt(10000 - JSBI.toNumber(allowedSlippage.numerator))),
JSBI.BigInt(10000)
)
),
@@ -220,11 +237,13 @@ function V2PairMigration({
)
const refund0 = useMemo(
() => position && new TokenAmount(token0, JSBI.subtract(token0Value.raw, position.amount0.raw)),
() =>
position && CurrencyAmount.fromRawAmount(token0, JSBI.subtract(token0Value.quotient, position.amount0.quotient)),
[token0Value, position, token0]
)
const refund1 = useMemo(
() => position && new TokenAmount(token1, JSBI.subtract(token1Value.raw, position.amount1.raw)),
() =>
position && CurrencyAmount.fromRawAmount(token1, JSBI.subtract(token1Value.quotient, position.amount1.quotient)),
[token1Value, position, token1]
)
@@ -281,7 +300,7 @@ function V2PairMigration({
data.push(
migrator.interface.encodeFunctionData('selfPermit', [
pair.address,
`0x${pairBalance.raw.toString(16)}`,
`0x${pairBalance.quotient.toString(16)}`,
deadlineToUse,
signatureData.v,
signatureData.r,
@@ -307,15 +326,15 @@ function V2PairMigration({
migrator.interface.encodeFunctionData('migrate', [
{
pair: pair.address,
liquidityToMigrate: `0x${pairBalance.raw.toString(16)}`,
liquidityToMigrate: `0x${pairBalance.quotient.toString(16)}`,
percentageToMigrate,
token0: token0.address,
token1: token1.address,
fee: feeAmount,
tickLower,
tickUpper,
amount0Min: `0x${v3Amount0Min.raw.toString(16)}`,
amount1Min: `0x${v3Amount1Min.raw.toString(16)}`,
amount0Min: `0x${v3Amount0Min.quotient.toString(16)}`,
amount1Min: `0x${v3Amount1Min.quotient.toString(16)}`,
recipient: account,
deadline: deadlineToUse,
refundAsETH: true, // hard-code this for now
@@ -369,7 +388,7 @@ function V2PairMigration({
currency1,
])
const isSuccessfullyMigrated = !!pendingMigrationHash && JSBI.equal(pairBalance.raw, ZERO)
const isSuccessfullyMigrated = !!pendingMigrationHash && JSBI.equal(pairBalance.quotient, ZERO)
return (
<AutoColumn gap="20px">
@@ -644,14 +663,14 @@ export default function MigrateV2Pair({
const pairBalance = useTokenBalance(account ?? undefined, liquidityToken)
const totalSupply = useTotalSupply(liquidityToken)
const [reserve0Raw, reserve1Raw] = useSingleCallResult(pair, 'getReserves')?.result ?? []
const reserve0 = useMemo(() => (token0 && reserve0Raw ? new TokenAmount(token0, reserve0Raw) : undefined), [
token0,
reserve0Raw,
])
const reserve1 = useMemo(() => (token1 && reserve1Raw ? new TokenAmount(token1, reserve1Raw) : undefined), [
token1,
reserve1Raw,
])
const reserve0 = useMemo(
() => (token0 && reserve0Raw ? CurrencyAmount.fromRawAmount(token0, reserve0Raw) : undefined),
[token0, reserve0Raw]
)
const reserve1 = useMemo(
() => (token1 && reserve1Raw ? CurrencyAmount.fromRawAmount(token1, reserve1Raw) : undefined),
[token1, reserve1Raw]
)
// redirect for invalid url params
if (
@@ -673,7 +692,7 @@ export default function MigrateV2Pair({
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
<BackArrow to="/migrate/v2" />
<TYPE.mediumHeader>Migrate V2 Liquidity</TYPE.mediumHeader>
<SettingsTab />
<SettingsTab placeholderSlippage={DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE} />
</AutoRow>
{!account ? (

View File

@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
import { ExternalLink } from '../../theme'
import { AutoColumn } from 'components/Column'
import Squiggle from '../../assets/images/squiggle.png'
import Texture from '../../assets/images/sandtexture.png'
import Texture from '../../assets/images/sandtexture.webp'
import { RowBetween } from 'components/Row'
const CTASection = styled.section`
@@ -28,6 +28,7 @@ const CTA1 = styled(ExternalLink)`
border-radius: 20px;
display: flex;
flex-direction: column;
position: relative;
justify-content: space-between;
border: 1px solid ${({ theme }) => theme.bg3};
@@ -99,8 +100,8 @@ const HeaderText = styled(TYPE.label)`
`
const ResponsiveColumn = styled(AutoColumn)`
grid-template-columns: 1fr;
gap: 12px;
height: 100%;
${({ theme }) => theme.mediaWidth.upToMedium`
gap: 8px;
`};
@@ -108,6 +109,7 @@ const ResponsiveColumn = styled(AutoColumn)`
`
const StyledImage = styled.img`
height: 114px;
margin-top: -28px;
${({ theme }) => theme.mediaWidth.upToMedium`
height: 80px;
@@ -134,12 +136,10 @@ export default function CTACards() {
</CTA1>
<CTA2 href={'https://info.uniswap.org/#/pools'}>
<ResponsiveColumn>
<AutoColumn gap="0px">
<HeaderText style={{ alignSelf: 'flex-start' }}>{t('Top pools')}</HeaderText>
<TYPE.body fontWeight={300} style={{ alignSelf: 'flex-start' }}>
{t('Explore popular pools on Uniswap Analytics.')}
</TYPE.body>
</AutoColumn>
<HeaderText style={{ alignSelf: 'flex-start' }}>{t('Top pools')}</HeaderText>
<TYPE.body fontWeight={300} style={{ alignSelf: 'flex-start' }}>
{t('Explore popular pools on Uniswap Analytics.')}
</TYPE.body>
<HeaderText style={{ alignSelf: 'flex-end' }}>{t('↗')}</HeaderText>
</ResponsiveColumn>
</CTA2>

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from 'react'
import React, { SyntheticEvent, useCallback, useMemo, useRef, useState } from 'react'
import { NonfungiblePositionManager, Pool, Position } from '@uniswap/v3-sdk'
import { PoolState, usePool } from 'hooks/usePools'
@@ -16,14 +16,14 @@ import { ExternalLink, HideExtraSmall, TYPE } from 'theme'
import Badge from 'components/Badge'
import { calculateGasMargin, getEtherscanLink } from 'utils'
import { ButtonConfirmed, ButtonPrimary, ButtonGray } from 'components/Button'
import { DarkCard, DarkGreyCard, LightCard } from 'components/Card'
import { DarkCard, LightCard } from 'components/Card'
import CurrencyLogo from 'components/CurrencyLogo'
import { useTranslation } from 'react-i18next'
import { currencyId } from 'utils/currencyId'
import { formatTokenAmount } from 'utils/formatTokenAmount'
import { useV3PositionFees } from 'hooks/useV3PositionFees'
import { BigNumber } from '@ethersproject/bignumber'
import { WETH9, Currency, CurrencyAmount, Percent, Fraction, Price } from '@uniswap/sdk-core'
import { Token, WETH9, Currency, CurrencyAmount, Percent, Fraction, Price, currencyEquals } from '@uniswap/sdk-core'
import { useActiveWeb3React } from 'hooks'
import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
@@ -126,6 +126,23 @@ const ResponsiveButtonPrimary = styled(ButtonPrimary)`
`};
`
const NFTGrid = styled.div`
display: grid;
grid-template: 'overlap';
min-height: 400px;
`
const NFTCanvas = styled.canvas`
grid-area: overlap;
`
const NFTImage = styled.img`
grid-area: overlap;
height: 400px;
/* Ensures SVG appears on top of canvas. */
z-index: 1;
`
function CurrentPriceCard({
inverted,
pool,
@@ -147,15 +164,19 @@ function CurrentPriceCard({
<AutoColumn gap="8px" justify="center">
<ExtentsText>{t('Current price')}</ExtentsText>
<TYPE.mediumHeader textAlign="center">
{(inverted ? pool.token1Price : pool.token0Price).toSignificant(4)}{' '}
{(inverted ? pool.token1Price : pool.token0Price).toSignificant(5)}{' '}
</TYPE.mediumHeader>
<ExtentsText>{currencyQuote?.symbol + ' / ' + currencyBase?.symbol}</ExtentsText>
<ExtentsText>{currencyQuote?.symbol + ' per ' + currencyBase?.symbol}</ExtentsText>
</AutoColumn>
</LightCard>
)
}
function getRatio(lower: Price, current: Price, upper: Price) {
function getRatio(
lower: Price<Currency, Currency>,
current: Price<Currency, Currency>,
upper: Price<Currency, Currency>
) {
try {
if (!current.greaterThan(lower)) {
return 100
@@ -179,6 +200,50 @@ function getRatio(lower: Price, current: Price, upper: Price) {
}
}
function NFT({ image, height: targetHeight }: { image: string; height: number }) {
const [animate, setAnimate] = useState(false)
const canvasRef = useRef<HTMLCanvasElement>()
const imageRef = useRef<HTMLImageElement>()
const getSnapshot = (src: HTMLImageElement) => {
if (!canvasRef.current) return
const { current: canvas } = canvasRef
const context = canvas.getContext('2d')
if (!context) return
let { width, height } = src
// src may be hidden and not have the target dimensions
const ratio = width / height
height = targetHeight
width = Math.round(ratio * targetHeight)
// Ensure crispness at high DPIs
canvas.width = width * devicePixelRatio
canvas.height = height * devicePixelRatio
canvas.style.width = width + 'px'
canvas.style.height = height + 'px'
context.scale(devicePixelRatio, devicePixelRatio)
context.clearRect(0, 0, width, height)
context.drawImage(src, 0, 0, width, height)
}
const onLoad = (e: SyntheticEvent<HTMLImageElement>) => {
getSnapshot(e.target as HTMLImageElement)
}
return (
<NFTGrid onMouseEnter={() => setAnimate(true)} onMouseLeave={() => setAnimate(false)}>
<NFTCanvas ref={canvasRef as any} />
<NFTImage src={image} hidden={!animate} onLoad={onLoad} ref={imageRef as any} />
</NFTGrid>
)
}
export function PositionPage({
match: {
params: { tokenId: tokenIdFromUrl },
@@ -239,7 +304,7 @@ export function PositionPage({
}, [inverted, pool, priceLower, priceUpper])
// fees
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, positionDetails)
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, positionDetails?.tokenId)
const [collecting, setCollecting] = useState<boolean>(false)
const [collectMigrationHash, setCollectMigrationHash] = useState<string | null>(null)
@@ -255,8 +320,12 @@ export function PositionPage({
const { calldata, value } = NonfungiblePositionManager.collectCallParameters({
tokenId: tokenId.toString(),
expectedCurrencyOwed0: feeValue0.token.equals(WETH9[chainId]) ? CurrencyAmount.ether(feeValue0.raw) : feeValue0,
expectedCurrencyOwed1: feeValue1.token.equals(WETH9[chainId]) ? CurrencyAmount.ether(feeValue1.raw) : feeValue1,
expectedCurrencyOwed0: currencyEquals(feeValue0.currency, WETH9[chainId])
? CurrencyAmount.ether(feeValue0.quotient)
: feeValue0,
expectedCurrencyOwed1: currencyEquals(feeValue1.currency, WETH9[chainId])
? CurrencyAmount.ether(feeValue1.quotient)
: feeValue1,
recipient: account,
})
@@ -285,11 +354,11 @@ export function PositionPage({
ReactGA.event({
category: 'Liquidity',
action: 'CollectV3',
label: [feeValue0.token.symbol, feeValue1.token.symbol].join('/'),
label: [feeValue0.currency.symbol, feeValue1.currency.symbol].join('/'),
})
addTransaction(response, {
summary: `Collect ${feeValue0.token.symbol}/${feeValue1.token.symbol} fees`,
summary: `Collect ${feeValue0.currency.symbol}/${feeValue1.currency.symbol} fees`,
})
})
})
@@ -305,14 +374,14 @@ export function PositionPage({
const price0 = useUSDCPrice(token0 ?? undefined)
const price1 = useUSDCPrice(token1 ?? undefined)
const fiatValueOfFees: CurrencyAmount | null = useMemo(() => {
const fiatValueOfFees: CurrencyAmount<Token> | null = useMemo(() => {
if (!price0 || !price1 || !feeValue0 || !feeValue1) return null
const amount0 = price0.quote(feeValue0)
const amount1 = price1.quote(feeValue1)
return amount0.add(amount1)
}, [price0, price1, feeValue0, feeValue1])
const fiatValueOfLiquidity: CurrencyAmount | null = useMemo(() => {
const fiatValueOfLiquidity: CurrencyAmount<Token> | null = useMemo(() => {
if (!price0 || !price1 || !position) return null
const amount0 = price0.quote(position.amount0)
const amount1 = price1.quote(position.amount1)
@@ -420,7 +489,7 @@ export function PositionPage({
borderRadius="12px"
style={{ marginRight: '8px' }}
>
{t('Add Liquidity')}
{t('Increase Liquidity')}
</ButtonGray>
) : null}
{tokenId && !removed ? (
@@ -453,7 +522,7 @@ export function PositionPage({
}}
>
<div style={{ marginRight: 12 }}>
<img height="400px" src={metadata.result.image} />
<NFT image={metadata.result.image} height={400} />
</div>
{typeof chainId === 'number' && owner && !ownsNFT ? (
<ExternalLink href={getEtherscanLink(chainId, owner, 'address')}>Owner</ExternalLink>
@@ -498,11 +567,9 @@ export function PositionPage({
{inverted ? position?.amount0.toSignificant(4) : position?.amount1.toSignificant(4)}
</TYPE.main>
{typeof ratio === 'number' && !removed ? (
<DarkGreyCard padding="4px 6px" style={{ width: 'fit-content', marginLeft: '8px' }}>
<TYPE.main color={theme.text2} fontSize={11}>
{inverted ? ratio : 100 - ratio}%
</TYPE.main>
</DarkGreyCard>
<Badge style={{ marginLeft: '10px' }}>
<TYPE.main fontSize={11}>{inverted ? ratio : 100 - ratio}%</TYPE.main>
</Badge>
) : null}
</RowFixed>
</RowBetween>
@@ -516,11 +583,11 @@ export function PositionPage({
{inverted ? position?.amount1.toSignificant(4) : position?.amount0.toSignificant(4)}
</TYPE.main>
{typeof ratio === 'number' && !removed ? (
<DarkGreyCard padding="4px 6px" style={{ width: 'fit-content', marginLeft: '8px' }}>
<Badge style={{ marginLeft: '10px' }}>
<TYPE.main color={theme.text2} fontSize={11}>
{inverted ? 100 - ratio : ratio}%
</TYPE.main>
</DarkGreyCard>
</Badge>
) : null}
</RowFixed>
</RowBetween>
@@ -640,8 +707,8 @@ export function PositionPage({
<LightCard padding="12px" width="100%">
<AutoColumn gap="8px" justify="center">
<ExtentsText>Min price</ExtentsText>
<TYPE.mediumHeader textAlign="center">{priceLower?.toSignificant(4)}</TYPE.mediumHeader>
<ExtentsText> {currencyQuote?.symbol + ' / ' + currencyBase?.symbol}</ExtentsText>
<TYPE.mediumHeader textAlign="center">{priceLower?.toSignificant(5)}</TYPE.mediumHeader>
<ExtentsText> {currencyQuote?.symbol + ' per ' + currencyBase?.symbol}</ExtentsText>
{inRange && (
<TYPE.small color={theme.text3}>
@@ -655,8 +722,8 @@ export function PositionPage({
<LightCard padding="12px" width="100%">
<AutoColumn gap="8px" justify="center">
<ExtentsText>Max price</ExtentsText>
<TYPE.mediumHeader textAlign="center">{priceUpper?.toSignificant(4)}</TYPE.mediumHeader>
<ExtentsText> {currencyQuote?.symbol + ' / ' + currencyBase?.symbol}</ExtentsText>
<TYPE.mediumHeader textAlign="center">{priceUpper?.toSignificant(5)}</TYPE.mediumHeader>
<ExtentsText> {currencyQuote?.symbol + ' per ' + currencyBase?.symbol}</ExtentsText>
{inRange && (
<TYPE.small color={theme.text3}>

View File

@@ -1,5 +1,5 @@
import React, { useContext } from 'react'
import { ButtonGray, ButtonPrimary } from 'components/Button'
import { ButtonGray, ButtonOutlined, ButtonPrimary } from 'components/Button'
import { AutoColumn } from 'components/Column'
import { FlyoutAlignment, NewMenu } from 'components/Menu'
import { SwapPoolTabs } from 'components/NavigationTabs'
@@ -217,14 +217,40 @@ export default function Pool() {
)}
</MainContentWrapper>
<RowFixed justify="center" style={{ width: '100%' }}>
<ButtonGray
<ButtonOutlined
as={Link}
to="/pool/v2"
id="import-pool-link"
style={{ padding: '8px 16px', borderRadius: '12px', width: 'fit-content' }}
style={{
padding: '8px 16px',
margin: '0 4px',
borderRadius: '12px',
width: 'fit-content',
fontSize: '14px',
}}
>
<TYPE.subHeader>{t('Looking for your V2 Liquidity')}?</TYPE.subHeader>
</ButtonGray>
<Layers size={14} style={{ marginRight: '8px' }} />
{t('View V2 Liquidity')}
</ButtonOutlined>
{positions && positions.length > 0 && (
<ButtonOutlined
as={Link}
to="/migrate/v2"
id="import-pool-link"
style={{
padding: '8px 16px',
margin: '0 4px',
borderRadius: '12px',
width: 'fit-content',
fontSize: '14px',
}}
>
<ChevronsRight size={16} style={{ marginRight: '8px' }} />
{t('Migrate Liquidity')}
</ButtonOutlined>
)}
</RowFixed>
</AutoColumn>
</AutoColumn>

View File

@@ -9,7 +9,9 @@ import { ExternalLink, TYPE, HideSmall } from '../../theme'
import { Text } from 'rebass'
import Card from '../../components/Card'
import { RowBetween, RowFixed } from '../../components/Row'
import { ButtonPrimary, ButtonSecondary } from '../../components/Button'
import { ButtonPrimary, ButtonSecondary, ButtonOutlined } from '../../components/Button'
import { ChevronsRight } from 'react-feather'
import { AutoColumn } from '../../components/Column'
import { useActiveWeb3React } from '../../hooks'
@@ -108,7 +110,9 @@ export default function Pool() {
// show liquidity even if its deposited in rewards contract
const stakingInfo = useStakingInfo()
const stakingInfosWithBalance = stakingInfo?.filter((pool) => JSBI.greaterThan(pool.stakedAmount.raw, BIG_INT_ZERO))
const stakingInfosWithBalance = stakingInfo?.filter((pool) =>
JSBI.greaterThan(pool.stakedAmount.quotient, BIG_INT_ZERO)
)
const stakingPairs = useV2Pairs(stakingInfosWithBalance?.map((stakingInfo) => stakingInfo.tokens))
// remove any pairs that also are included in pairs with stake in mining pool
@@ -151,7 +155,7 @@ export default function Pool() {
</VoteCard>
<AutoColumn gap="lg" justify="center">
<AutoColumn gap="lg" style={{ width: '100%' }}>
<AutoColumn gap="md" style={{ width: '100%' }}>
<TitleRow style={{ marginTop: '1rem' }} padding={'0'}>
<HideSmall>
<TYPE.mediumHeader style={{ marginTop: '0.5rem', justifySelf: 'flex-start' }}>
@@ -170,7 +174,7 @@ export default function Pool() {
to="/add/v2/ETH"
>
<Text fontWeight={500} fontSize={16}>
Add Liquidity
Add V2 Liquidity
</Text>
</ResponsiveButtonPrimary>
</ButtonRow>
@@ -211,6 +215,23 @@ export default function Pool() {
/>
)
)}
<RowFixed justify="center" style={{ width: '100%' }}>
<ButtonOutlined
as={Link}
to="/migrate/v2"
id="import-pool-link"
style={{
padding: '8px 16px',
margin: '0 4px',
borderRadius: '12px',
width: 'fit-content',
fontSize: '14px',
}}
>
<ChevronsRight size={16} style={{ marginRight: '8px' }} />
Migrate Liquidity to V3
</ButtonOutlined>
</RowFixed>
</>
) : (
<EmptyProposals>

View File

@@ -1,5 +1,5 @@
import { Currency, ETHER, TokenAmount } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import { Currency, ETHER, CurrencyAmount, Token } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import React, { useCallback, useEffect, useState } from 'react'
import { Plus } from 'react-feather'
import { Text } from 'rebass'
@@ -56,12 +56,12 @@ export default function PoolFinder() {
Boolean(
pairState === PairState.EXISTS &&
pair &&
JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) &&
JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0))
JSBI.equal(pair.reserve0.quotient, JSBI.BigInt(0)) &&
JSBI.equal(pair.reserve1.quotient, JSBI.BigInt(0))
)
const position: TokenAmount | undefined = useTokenBalance(account ?? undefined, pair?.liquidityToken)
const hasPosition = Boolean(position && JSBI.greaterThan(position.raw, JSBI.BigInt(0)))
const position: CurrencyAmount<Token> | undefined = useTokenBalance(account ?? undefined, pair?.liquidityToken)
const hasPosition = Boolean(position && JSBI.greaterThan(position.quotient, JSBI.BigInt(0)))
const handleCurrencySelect = useCallback(
(currency: Currency) => {

View File

@@ -15,13 +15,13 @@ import { Text } from 'rebass'
import CurrencyLogo from 'components/CurrencyLogo'
import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount'
import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import { useUserSlippageTolerance } from 'state/user/hooks'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import ReactGA from 'react-ga'
import { useActiveWeb3React } from 'hooks'
import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from 'state/transactions/hooks'
import { WETH9, CurrencyAmount } from '@uniswap/sdk-core'
import { WETH9, CurrencyAmount, currencyEquals, Percent } from '@uniswap/sdk-core'
import { TYPE } from 'theme'
import { Wrapper, SmallMaxButton, ResponsiveHeaderText } from './styled'
import Loader from 'components/Loader'
@@ -37,6 +37,8 @@ import RangeBadge from 'components/Badge/RangeBadge'
export const UINT128MAX = BigNumber.from(2).pow(128).sub(1)
const DEFAULT_REMOVE_V3_LIQUIDITY_SLIPPAGE_TOLERANCE = new Percent(5, 100)
// redirect invalid tokenIds
export default function RemoveLiquidityV3({
location,
@@ -89,7 +91,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
const [percentForSlider, onPercentSelectForSlider] = useDebouncedChangeHandler(percent, onPercentSelect)
const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_REMOVE_V3_LIQUIDITY_SLIPPAGE_TOLERANCE) // custom from users
const [showConfirm, setShowConfirm] = useState(false)
const [attemptingTxn, setAttemptingTxn] = useState(false)
@@ -120,11 +122,11 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
slippageTolerance: allowedSlippage,
deadline: deadline.toString(),
collectOptions: {
expectedCurrencyOwed0: liquidityValue0.token.equals(WETH9[chainId])
? CurrencyAmount.ether(feeValue0.raw)
expectedCurrencyOwed0: currencyEquals(liquidityValue0.currency, WETH9[chainId])
? CurrencyAmount.ether(feeValue0.quotient)
: feeValue0,
expectedCurrencyOwed1: liquidityValue1.token.equals(WETH9[chainId])
? CurrencyAmount.ether(feeValue1.raw)
expectedCurrencyOwed1: currencyEquals(liquidityValue1.currency, WETH9[chainId])
? CurrencyAmount.ether(feeValue1.quotient)
: feeValue1,
recipient: account,
},
@@ -152,12 +154,12 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
ReactGA.event({
category: 'Liquidity',
action: 'RemoveV3',
label: [liquidityValue0.token.symbol, liquidityValue1.token.symbol].join('/'),
label: [liquidityValue0.currency.symbol, liquidityValue1.currency.symbol].join('/'),
})
setTxnHash(response.hash)
setAttemptingTxn(false)
addTransaction(response, {
summary: `Remove ${liquidityValue0.token.symbol}/${liquidityValue1.token.symbol} V3 liquidity`,
summary: `Remove ${liquidityValue0.currency.symbol}/${liquidityValue1.currency.symbol} V3 liquidity`,
})
})
})
@@ -274,7 +276,12 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
pendingText={pendingText}
/>
<AppBody>
<AddRemoveTabs creating={false} adding={false} positionID={tokenId.toString()} />
<AddRemoveTabs
creating={false}
adding={false}
positionID={tokenId.toString()}
defaultSlippage={DEFAULT_REMOVE_V3_LIQUIDITY_SLIPPAGE_TOLERANCE}
/>
<Wrapper>
{position ? (
<AutoColumn gap="lg">

View File

@@ -1,6 +1,6 @@
import { Contract } from '@ethersproject/contracts'
import { TransactionResponse } from '@ethersproject/providers'
import { Currency, currencyEquals, ETHER, Percent, WETH9 } from '@uniswap/sdk-core'
import { Currency, currencyEquals, Percent, WETH9 } from '@uniswap/sdk-core'
import React, { useCallback, useContext, useMemo, useState } from 'react'
import { ArrowDown, Plus } from 'react-feather'
import ReactGA from 'react-ga'
@@ -40,9 +40,11 @@ import { useBurnActionHandlers } from '../../state/burn/hooks'
import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks'
import { Field } from '../../state/burn/actions'
import { useWalletModalToggle } from '../../state/application/hooks'
import { useUserSlippageTolerance } from '../../state/user/hooks'
import { useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { BigNumber } from '@ethersproject/bignumber'
const DEFAULT_REMOVE_LIQUIDITY_SLIPPAGE_TOLERANCE = new Percent(5, 100)
export default function RemoveLiquidity({
history,
match: {
@@ -76,7 +78,7 @@ export default function RemoveLiquidity({
// txn values
const [txHash, setTxHash] = useState<string>('')
const deadline = useTransactionDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_REMOVE_LIQUIDITY_SLIPPAGE_TOLERANCE)
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
@@ -161,8 +163,8 @@ export default function RemoveLiquidity({
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
const currencyBIsETH = currencyB.isEther
const oneCurrencyIsETH = currencyA.isEther || currencyBIsETH
if (!tokenA || !tokenB) throw new Error('could not wrap')
@@ -174,7 +176,7 @@ export default function RemoveLiquidity({
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
liquidityAmount.quotient.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
@@ -187,7 +189,7 @@ export default function RemoveLiquidity({
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
liquidityAmount.quotient.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
@@ -202,7 +204,7 @@ export default function RemoveLiquidity({
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
liquidityAmount.quotient.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
@@ -219,7 +221,7 @@ export default function RemoveLiquidity({
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
liquidityAmount.quotient.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
@@ -381,7 +383,7 @@ export default function RemoveLiquidity({
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
const oneCurrencyIsETH = currencyA?.isEther || currencyB?.isEther
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH9[chainId], currencyA)) ||
@@ -426,7 +428,7 @@ export default function RemoveLiquidity({
return (
<>
<AppBody>
<AddRemoveTabs creating={false} adding={false} />
<AddRemoveTabs creating={false} adding={false} defaultSlippage={DEFAULT_REMOVE_LIQUIDITY_SLIPPAGE_TOLERANCE} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
@@ -524,8 +526,8 @@ export default function RemoveLiquidity({
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/v2/${currencyA === ETHER ? WETH9[chainId].address : currencyIdA}/${
currencyB === ETHER ? WETH9[chainId].address : currencyIdB
to={`/remove/v2/${currencyA?.isEther ? WETH9[chainId].address : currencyIdA}/${
currencyB?.isEther ? WETH9[chainId].address : currencyIdB
}`}
>
Receive WETH

View File

@@ -1,4 +1,4 @@
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { AdvancedSwapDetails } from 'components/swap/AdvancedSwapDetails'
@@ -46,7 +46,7 @@ import {
useSwapActionHandlers,
useSwapState,
} from '../../state/swap/hooks'
import { useExpertModeManager, useUserSingleHopOnly, useUserSlippageTolerance } from '../../state/user/hooks'
import { useExpertModeManager, useUserSingleHopOnly } from '../../state/user/hooks'
import { HideSmall, LinkStyledButton, TYPE } from '../../theme'
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage'
@@ -76,7 +76,7 @@ export default function Swap({ history }: RouteComponentProps) {
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c?.isToken ?? false) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
@@ -100,19 +100,21 @@ export default function Swap({ history }: RouteComponentProps) {
// for expert mode
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
const [allowedSlippage] = useUserSlippageTolerance()
// get version from the url
const toggledVersion = useToggledVersion()
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const {
v2Trade,
v3TradeState: { trade: v3Trade, state: v3TradeState },
toggledTrade: trade,
allowedSlippage,
currencyBalances,
parsedAmount,
currencies,
inputError: swapInputError,
} = useDerivedSwapInfo()
} = useDerivedSwapInfo(toggledVersion)
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
@@ -121,13 +123,6 @@ export default function Swap({ history }: RouteComponentProps) {
)
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
const { address: recipientAddress } = useENSAddress(recipient)
const toggledVersion = useToggledVersion()
const trade = showWrap
? undefined
: {
[Version.v2]: v2Trade,
[Version.v3]: v3Trade ?? undefined,
}[toggledVersion]
const parsedAmounts = useMemo(
() =>
@@ -173,7 +168,7 @@ export default function Swap({ history }: RouteComponentProps) {
// modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean
tradeToConfirm: V2Trade | V3Trade | undefined
tradeToConfirm: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | undefined
attemptingTxn: boolean
swapErrorMessage: string | undefined
txHash: string | undefined
@@ -230,7 +225,7 @@ export default function Swap({ history }: RouteComponentProps) {
}
}, [approvalState, approvalSubmitted])
const maxInputAmount: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const maxInputAmount: CurrencyAmount<Currency> | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount))
// the callback to execute the swap
@@ -357,7 +352,7 @@ export default function Swap({ history }: RouteComponentProps) {
onDismiss={handleDismissTokenWarning}
/>
<AppBody>
<SwapHeader />
<SwapHeader allowedSlippage={allowedSlippage} />
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}
@@ -484,7 +479,9 @@ export default function Swap({ history }: RouteComponentProps) {
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
<MouseoverTooltipContent content={<AdvancedSwapDetails trade={trade} />}>
<MouseoverTooltipContent
content={<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} />}
>
<StyledInfo />
</MouseoverTooltipContent>
</RowFixed>

View File

@@ -19,8 +19,8 @@ import {
import { DateTime } from 'luxon'
import ReactMarkdown from 'react-markdown'
import VoteModal from '../../components/vote/VoteModal'
import { TokenAmount } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import { Token, CurrencyAmount } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import { useActiveWeb3React } from '../../hooks'
import { AVERAGE_BLOCK_TIME_IN_SECS, COMMON_CONTRACT_NAMES, UNI, ZERO_ADDRESS } from '../../constants'
import { getEtherscanLink, isAddress } from '../../utils'
@@ -152,21 +152,24 @@ export default function VotePage({
proposalData && totalVotes ? ((proposalData.againstCount * 100) / totalVotes).toFixed(0) + '%' : '0%'
// only count available votes as of the proposal start block
const availableVotes: TokenAmount | undefined = useUserVotesAsOfBlock(proposalData?.startBlock ?? undefined)
const availableVotes: CurrencyAmount<Token> | undefined = useUserVotesAsOfBlock(proposalData?.startBlock ?? undefined)
// only show voting if user has > 0 votes at proposal start block and proposal is active,
const showVotingButtons =
availableVotes &&
JSBI.greaterThan(availableVotes.raw, JSBI.BigInt(0)) &&
JSBI.greaterThan(availableVotes.quotient, JSBI.BigInt(0)) &&
proposalData &&
proposalData.status === ProposalState.Active
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, chainId ? UNI[chainId] : undefined)
const uniBalance: CurrencyAmount<Token> | undefined = useTokenBalance(
account ?? undefined,
chainId ? UNI[chainId] : undefined
)
const userDelegatee: string | undefined = useUserDelegatee()
// in blurb link to home page if they are able to unlock
const showLinkForUnlock = Boolean(
uniBalance && JSBI.notEqual(uniBalance.raw, JSBI.BigInt(0)) && userDelegatee === ZERO_ADDRESS
uniBalance && JSBI.notEqual(uniBalance.quotient, JSBI.BigInt(0)) && userDelegatee === ZERO_ADDRESS
)
// show links in propsoal details if content is an address

View File

@@ -20,8 +20,8 @@ import DelegateModal from '../../components/vote/DelegateModal'
import { useTokenBalance } from '../../state/wallet/hooks'
import { useActiveWeb3React } from '../../hooks'
import { UNI, ZERO_ADDRESS } from '../../constants'
import { TokenAmount, ChainId } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import { Token, CurrencyAmount, ChainId } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import { shortenAddress, getEtherscanLink } from '../../utils'
import Loader from '../../components/Loader'
import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
@@ -119,13 +119,16 @@ export default function Vote() {
const allProposals: ProposalData[] = useAllProposalData()
// user data
const availableVotes: TokenAmount | undefined = useUserVotes()
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, chainId ? UNI[chainId] : undefined)
const availableVotes: CurrencyAmount<Token> | undefined = useUserVotes()
const uniBalance: CurrencyAmount<Token> | undefined = useTokenBalance(
account ?? undefined,
chainId ? UNI[chainId] : undefined
)
const userDelegatee: string | undefined = useUserDelegatee()
// show delegation option if they have have a balance, but have not delegated
const showUnlockVoting = Boolean(
uniBalance && JSBI.notEqual(uniBalance.raw, JSBI.BigInt(0)) && userDelegatee === ZERO_ADDRESS
uniBalance && JSBI.notEqual(uniBalance.quotient, JSBI.BigInt(0)) && userDelegatee === ZERO_ADDRESS
)
return (
@@ -176,14 +179,14 @@ export default function Vote() {
>
Unlock Voting
</ButtonPrimary>
) : availableVotes && JSBI.notEqual(JSBI.BigInt(0), availableVotes?.raw) ? (
) : availableVotes && JSBI.notEqual(JSBI.BigInt(0), availableVotes?.quotient) ? (
<TYPE.body fontWeight={500} mr="6px">
<FormattedCurrencyAmount currencyAmount={availableVotes} /> Votes
</TYPE.body>
) : uniBalance &&
userDelegatee &&
userDelegatee !== ZERO_ADDRESS &&
JSBI.notEqual(JSBI.BigInt(0), uniBalance?.raw) ? (
JSBI.notEqual(JSBI.BigInt(0), uniBalance?.quotient) ? (
<TYPE.body fontWeight={500} mr="6px">
<FormattedCurrencyAmount currencyAmount={uniBalance} /> Votes
</TYPE.body>
@@ -235,7 +238,7 @@ export default function Vote() {
})}
</TopSection>
<TYPE.subHeader color="text3">
A minimum threshhold of 1% of the total UNI supply is required to submit proposals
A minimum threshold of 1% of the total UNI supply is required to submit proposals
</TYPE.subHeader>
</PageWrapper>
)

View File

@@ -1,5 +1,6 @@
import { Currency, CurrencyAmount, Percent, TokenAmount } from '@uniswap/sdk-core'
import { JSBI, Pair } from '@uniswap/v2-sdk'
import JSBI from 'jsbi'
import { Token, Currency, Percent, CurrencyAmount } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { useCallback } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useV2Pair } from '../../hooks/useV2Pairs'
@@ -23,9 +24,9 @@ export function useDerivedBurnInfo(
pair?: Pair | null
parsedAmounts: {
[Field.LIQUIDITY_PERCENT]: Percent
[Field.LIQUIDITY]?: TokenAmount
[Field.CURRENCY_A]?: CurrencyAmount
[Field.CURRENCY_B]?: CurrencyAmount
[Field.LIQUIDITY]?: CurrencyAmount<Token>
[Field.CURRENCY_A]?: CurrencyAmount<Currency>
[Field.CURRENCY_B]?: CurrencyAmount<Currency>
}
error?: string
} {
@@ -38,7 +39,7 @@ export function useDerivedBurnInfo(
// balances
const relevantTokenBalances = useTokenBalances(account ?? undefined, [pair?.liquidityToken])
const userLiquidity: undefined | TokenAmount = relevantTokenBalances?.[pair?.liquidityToken?.address ?? '']
const userLiquidity: undefined | CurrencyAmount<Token> = relevantTokenBalances?.[pair?.liquidityToken?.address ?? '']
const [tokenA, tokenB] = [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
const tokens = {
@@ -55,8 +56,8 @@ export function useDerivedBurnInfo(
userLiquidity &&
tokenA &&
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
JSBI.greaterThanOrEqual(totalSupply.raw, userLiquidity.raw)
? new TokenAmount(tokenA, pair.getLiquidityValue(tokenA, totalSupply, userLiquidity, false).raw)
JSBI.greaterThanOrEqual(totalSupply.quotient, userLiquidity.quotient)
? CurrencyAmount.fromRawAmount(tokenA, pair.getLiquidityValue(tokenA, totalSupply, userLiquidity, false).quotient)
: undefined
const liquidityValueB =
pair &&
@@ -64,10 +65,10 @@ export function useDerivedBurnInfo(
userLiquidity &&
tokenB &&
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
JSBI.greaterThanOrEqual(totalSupply.raw, userLiquidity.raw)
? new TokenAmount(tokenB, pair.getLiquidityValue(tokenB, totalSupply, userLiquidity, false).raw)
JSBI.greaterThanOrEqual(totalSupply.quotient, userLiquidity.quotient)
? CurrencyAmount.fromRawAmount(tokenB, pair.getLiquidityValue(tokenB, totalSupply, userLiquidity, false).quotient)
: undefined
const liquidityValues: { [Field.CURRENCY_A]?: TokenAmount; [Field.CURRENCY_B]?: TokenAmount } = {
const liquidityValues: { [Field.CURRENCY_A]?: CurrencyAmount<Token>; [Field.CURRENCY_B]?: CurrencyAmount<Token> } = {
[Field.CURRENCY_A]: liquidityValueA,
[Field.CURRENCY_B]: liquidityValueB,
}
@@ -82,7 +83,7 @@ export function useDerivedBurnInfo(
if (pair?.liquidityToken) {
const independentAmount = tryParseAmount(typedValue, pair.liquidityToken)
if (independentAmount && userLiquidity && !independentAmount.greaterThan(userLiquidity)) {
percentToRemove = new Percent(independentAmount.raw, userLiquidity.raw)
percentToRemove = new Percent(independentAmount.quotient, userLiquidity.quotient)
}
}
}
@@ -92,29 +93,32 @@ export function useDerivedBurnInfo(
const independentAmount = tryParseAmount(typedValue, tokens[independentField])
const liquidityValue = liquidityValues[independentField]
if (independentAmount && liquidityValue && !independentAmount.greaterThan(liquidityValue)) {
percentToRemove = new Percent(independentAmount.raw, liquidityValue.raw)
percentToRemove = new Percent(independentAmount.quotient, liquidityValue.quotient)
}
}
}
const parsedAmounts: {
[Field.LIQUIDITY_PERCENT]: Percent
[Field.LIQUIDITY]?: TokenAmount
[Field.CURRENCY_A]?: TokenAmount
[Field.CURRENCY_B]?: TokenAmount
[Field.LIQUIDITY]?: CurrencyAmount<Token>
[Field.CURRENCY_A]?: CurrencyAmount<Currency>
[Field.CURRENCY_B]?: CurrencyAmount<Currency>
} = {
[Field.LIQUIDITY_PERCENT]: percentToRemove,
[Field.LIQUIDITY]:
userLiquidity && percentToRemove && percentToRemove.greaterThan('0')
? new TokenAmount(userLiquidity.token, percentToRemove.multiply(userLiquidity.raw).quotient)
? CurrencyAmount.fromRawAmount(
userLiquidity.currency,
percentToRemove.multiply(userLiquidity.quotient).quotient
)
: undefined,
[Field.CURRENCY_A]:
tokenA && percentToRemove && percentToRemove.greaterThan('0') && liquidityValueA
? new TokenAmount(tokenA, percentToRemove.multiply(liquidityValueA.raw).quotient)
? CurrencyAmount.fromRawAmount(tokenA, percentToRemove.multiply(liquidityValueA.quotient).quotient)
: undefined,
[Field.CURRENCY_B]:
tokenB && percentToRemove && percentToRemove.greaterThan('0') && liquidityValueB
? new TokenAmount(tokenB, percentToRemove.multiply(liquidityValueB.raw).quotient)
? CurrencyAmount.fromRawAmount(tokenB, percentToRemove.multiply(liquidityValueB.quotient).quotient)
: undefined,
}

View File

@@ -1,4 +1,4 @@
import { TokenAmount, Percent } from '@uniswap/sdk-core'
import { Token, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { Position } from '@uniswap/v3-sdk'
import { usePool } from 'hooks/usePools'
import { useActiveWeb3React } from 'hooks'
@@ -20,10 +20,10 @@ export function useDerivedV3BurnInfo(
): {
position?: Position
liquidityPercentage?: Percent
liquidityValue0?: TokenAmount
liquidityValue1?: TokenAmount
feeValue0?: TokenAmount
feeValue1?: TokenAmount
liquidityValue0?: CurrencyAmount<Token>
liquidityValue1?: CurrencyAmount<Token>
feeValue0?: CurrencyAmount<Token>
feeValue1?: CurrencyAmount<Token>
outOfRange: boolean
error?: string
} {
@@ -52,12 +52,18 @@ export function useDerivedV3BurnInfo(
const liquidityValue0 =
positionSDK &&
new TokenAmount(positionSDK.amount0.token, liquidityPercentage.multiply(positionSDK.amount0.raw).quotient)
CurrencyAmount.fromRawAmount(
positionSDK.amount0.currency,
liquidityPercentage.multiply(positionSDK.amount0.quotient).quotient
)
const liquidityValue1 =
positionSDK &&
new TokenAmount(positionSDK.amount1.token, liquidityPercentage.multiply(positionSDK.amount1.raw).quotient)
CurrencyAmount.fromRawAmount(
positionSDK.amount1.currency,
liquidityPercentage.multiply(positionSDK.amount1.quotient).quotient
)
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, position)
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, position?.tokenId)
const outOfRange =
pool && position ? pool.tickCurrent < position.tickLower || pool.tickCurrent > position.tickUpper : false

View File

@@ -1,6 +1,6 @@
import { UNI } from './../../constants/index'
import { JSBI } from '@uniswap/v2-sdk'
import { TokenAmount, ChainId } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import { CurrencyAmount, ChainId, Token } from '@uniswap/sdk-core'
import { TransactionResponse } from '@ethersproject/providers'
import { useEffect, useState } from 'react'
import { useActiveWeb3React } from '../../hooks'
@@ -74,7 +74,7 @@ export function useUserHasAvailableClaim(account: string | null | undefined): bo
return Boolean(userClaimData && !isClaimedResult.loading && isClaimedResult.result?.[0] === false)
}
export function useUserUnclaimedAmount(account: string | null | undefined): TokenAmount | undefined {
export function useUserUnclaimedAmount(account: string | null | undefined): CurrencyAmount<Token> | undefined {
const { chainId } = useActiveWeb3React()
const userClaimData = useUserClaimData(account)
const canClaim = useUserHasAvailableClaim(account)
@@ -82,9 +82,9 @@ export function useUserUnclaimedAmount(account: string | null | undefined): Toke
const uni = chainId ? UNI[chainId] : undefined
if (!uni) return undefined
if (!canClaim || !userClaimData) {
return new TokenAmount(uni, JSBI.BigInt(0))
return CurrencyAmount.fromRawAmount(uni, JSBI.BigInt(0))
}
return new TokenAmount(uni, JSBI.BigInt(userClaimData.amount))
return CurrencyAmount.fromRawAmount(uni, JSBI.BigInt(userClaimData.amount))
}
export function useClaimCallback(
@@ -97,7 +97,7 @@ export function useClaimCallback(
const claimData = useUserClaimData(account)
// used for popup summary
const unclaimedAmount: TokenAmount | undefined = useUserUnclaimedAmount(account)
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
const addTransaction = useTransactionAdder()
const distributorContract = useMerkleDistributorContract()

View File

@@ -1,5 +1,5 @@
import { UNI, PRELOADED_PROPOSALS } from './../../constants/index'
import { TokenAmount } from '@uniswap/sdk-core'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { isAddress } from 'ethers/lib/utils'
import { useGovernanceContract, useUniContract } from '../../hooks/useContract'
import { useSingleCallResult, useSingleContractMultipleData } from '../multicall/hooks'
@@ -164,18 +164,18 @@ export function useUserDelegatee(): string {
}
// gets the users current votes
export function useUserVotes(): TokenAmount | undefined {
export function useUserVotes(): CurrencyAmount<Token> | undefined {
const { account, chainId } = useActiveWeb3React()
const uniContract = useUniContract()
// check for available votes
const uni = chainId ? UNI[chainId] : undefined
const votes = useSingleCallResult(uniContract, 'getCurrentVotes', [account ?? undefined])?.result?.[0]
return votes && uni ? new TokenAmount(uni, votes) : undefined
return votes && uni ? CurrencyAmount.fromRawAmount(uni, votes) : undefined
}
// fetch available votes as of block (usually proposal start block)
export function useUserVotesAsOfBlock(block: number | undefined): TokenAmount | undefined {
export function useUserVotesAsOfBlock(block: number | undefined): CurrencyAmount<Token> | undefined {
const { account, chainId } = useActiveWeb3React()
const uniContract = useUniContract()
@@ -183,7 +183,7 @@ export function useUserVotesAsOfBlock(block: number | undefined): TokenAmount |
const uni = chainId ? UNI[chainId] : undefined
const votes = useSingleCallResult(uniContract, 'getPriorVotes', [account ?? undefined, block ?? undefined])
?.result?.[0]
return votes && uni ? new TokenAmount(uni, votes) : undefined
return votes && uni ? CurrencyAmount.fromRawAmount(uni, votes) : undefined
}
export function useDelegateCallback(): (delegatee: string | undefined) => undefined | Promise<string> {

View File

@@ -1,33 +1,13 @@
import { UNSUPPORTED_LIST_URLS } from './../../constants/lists'
import DEFAULT_TOKEN_LIST from '@uniswap/default-token-list'
import { ChainId, Token } from '@uniswap/sdk-core'
import { Tags, TokenInfo, TokenList } from '@uniswap/token-lists'
import { ChainId } from '@uniswap/sdk-core'
import { TokenList } from '@uniswap/token-lists'
import { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { AppState } from '../index'
import sortByListPriority from 'utils/listSort'
import UNSUPPORTED_TOKEN_LIST from '../../constants/tokenLists/uniswap-v2-unsupported.tokenlist.json'
type TagDetails = Tags[keyof Tags]
export interface TagInfo extends TagDetails {
id: string
}
/**
* Token instances created from token info.
*/
export class WrappedTokenInfo extends Token {
public readonly tokenInfo: TokenInfo
public readonly tags: TagInfo[]
constructor(tokenInfo: TokenInfo, tags: TagInfo[]) {
super(tokenInfo.chainId, tokenInfo.address, tokenInfo.decimals, tokenInfo.symbol, tokenInfo.name)
this.tokenInfo = tokenInfo
this.tags = tags
}
public get logoURI(): string | undefined {
return this.tokenInfo.logoURI
}
}
import { AppState } from '../index'
import { UNSUPPORTED_LIST_URLS } from './../../constants/lists'
import { WrappedTokenInfo } from './wrappedTokenInfo'
export type TokenAddressMap = Readonly<
{ [chainId in ChainId | number]: Readonly<{ [tokenAddress: string]: { token: WrappedTokenInfo; list: TokenList } }> }
@@ -53,14 +33,7 @@ export function listToTokenMap(list: TokenList): TokenAddressMap {
const map = list.tokens.reduce<TokenAddressMap>(
(tokenMap, tokenInfo) => {
const tags: TagInfo[] =
tokenInfo.tags
?.map((tagId) => {
if (!list.tags?.[tagId]) return undefined
return { ...list.tags[tagId], id: tagId }
})
?.filter((x): x is TagInfo => Boolean(x)) ?? []
const token = new WrappedTokenInfo(tokenInfo, tags)
const token = new WrappedTokenInfo(tokenInfo, list)
if (tokenMap[token.chainId][token.address] !== undefined) {
console.error(new Error(`Duplicate token! ${token.address}`))
return tokenMap
@@ -71,7 +44,7 @@ export function listToTokenMap(list: TokenList): TokenAddressMap {
...tokenMap[token.chainId as ChainId],
[token.address]: {
token,
list: list,
list,
},
},
}
@@ -84,14 +57,7 @@ export function listToTokenMap(list: TokenList): TokenAddressMap {
const TRANSFORMED_DEFAULT_TOKEN_LIST = listToTokenMap(DEFAULT_TOKEN_LIST)
export function useAllLists(): {
readonly [url: string]: {
readonly current: TokenList | null
readonly pendingUpdate: TokenList | null
readonly loadingRequestId: string | null
readonly error: string | null
}
} {
export function useAllLists(): AppState['lists']['byUrl'] {
return useSelector<AppState, AppState['lists']['byUrl']>((state) => state.lists.byUrl)
}
@@ -119,8 +85,7 @@ function useCombinedTokenMapFromUrls(urls: string[] | undefined): TokenAddressMa
const current = lists[currentUrl]?.current
if (!current) return allTokens
try {
const newTokens = Object.assign(listToTokenMap(current))
return combineMaps(allTokens, newTokens)
return combineMaps(allTokens, listToTokenMap(current))
} catch (error) {
console.error('Could not show token list due to error', error)
return allTokens
@@ -150,12 +115,6 @@ export function useCombinedActiveList(): TokenAddressMap {
return combineMaps(activeTokens, TRANSFORMED_DEFAULT_TOKEN_LIST)
}
// all tokens from inactive lists
export function useCombinedInactiveList(): TokenAddressMap {
const allInactiveListUrls: string[] = useInactiveListUrls()
return useCombinedTokenMapFromUrls(allInactiveListUrls)
}
// list of tokens not supported on interface, used to show warnings and prevent swaps and adds
export function useUnsupportedTokenList(): TokenAddressMap {
// get hard coded unsupported tokens
@@ -165,7 +124,10 @@ export function useUnsupportedTokenList(): TokenAddressMap {
const loadedUnsupportedListMap = useCombinedTokenMapFromUrls(UNSUPPORTED_LIST_URLS)
// format into one token address map
return combineMaps(localUnsupportedListMap, loadedUnsupportedListMap)
return useMemo(() => combineMaps(localUnsupportedListMap, loadedUnsupportedListMap), [
localUnsupportedListMap,
loadedUnsupportedListMap,
])
}
export function useIsListActive(url: string): boolean {

View File

@@ -9,7 +9,6 @@ import useIsWindowVisible from '../../hooks/useIsWindowVisible'
import { AppDispatch } from '../index'
import { acceptListUpdate } from './actions'
import { useActiveListUrls } from './hooks'
import { useAllInactiveTokens } from 'hooks/Tokens'
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
export default function Updater(): null {
@@ -21,9 +20,6 @@ export default function Updater(): null {
const lists = useAllLists()
const activeListUrls = useActiveListUrls()
// initiate loading
useAllInactiveTokens()
const fetchList = useFetchListCallback()
const fetchAllListsCallback = useCallback(() => {
if (!isWindowVisible) return

View File

@@ -0,0 +1,77 @@
import { ChainId, Token } from '@uniswap/sdk-core'
import { Tags, TokenInfo } from '@uniswap/token-lists'
import { TokenList } from '@uniswap/token-lists/dist/types'
import { isAddress } from '../../utils'
type TagDetails = Tags[keyof Tags]
export interface TagInfo extends TagDetails {
id: string
}
/**
* Token instances created from token info on a token list.
*/
export class WrappedTokenInfo implements Token {
public readonly isEther: false = false
public readonly isToken: true = true
public readonly list: TokenList
public readonly tokenInfo: TokenInfo
constructor(tokenInfo: TokenInfo, list: TokenList) {
this.tokenInfo = tokenInfo
this.list = list
}
private _checksummedAddress: string | null = null
public get address(): string {
if (this._checksummedAddress) return this._checksummedAddress
const checksummedAddress = isAddress(this.tokenInfo.address)
if (!checksummedAddress) throw new Error(`Invalid token address: ${this.tokenInfo.address}`)
return (this._checksummedAddress = checksummedAddress)
}
public get chainId(): ChainId | number {
return this.tokenInfo.chainId
}
public get decimals(): number {
return this.tokenInfo.decimals
}
public get name(): string {
return this.tokenInfo.name
}
public get symbol(): string {
return this.tokenInfo.symbol
}
public get logoURI(): string | undefined {
return this.tokenInfo.logoURI
}
private _tags: TagInfo[] | null = null
public get tags(): TagInfo[] {
if (this._tags !== null) return this._tags
if (!this.tokenInfo.tags) return (this._tags = [])
const listTags = this.list.tags
if (!listTags) return (this._tags = [])
return (this._tags = this.tokenInfo.tags.map((tagId) => {
return {
...listTags[tagId],
id: tagId,
}
}))
}
equals(other: Token): boolean {
return other.chainId === this.chainId && other.address.toLowerCase() === this.address.toLowerCase()
}
sortsBefore(other: Token): boolean {
if (this.equals(other)) throw new Error('Addresses should not be equal')
return this.address.toLowerCase() < other.address.toLowerCase()
}
}

View File

@@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'
import { AppDispatch, AppState } from '../index'
import { Field, typeInput } from './actions'
import { Pair } from '@uniswap/v2-sdk'
import { Currency, CurrencyAmount, ETHER, Percent, Price, TokenAmount } from '@uniswap/sdk-core'
import { Currency, Token, Percent, Price, CurrencyAmount } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import { PairState, useV2Pair } from '../../hooks/useV2Pairs'
import { useTotalSupply } from '../../hooks/useTotalSupply'
@@ -55,11 +55,11 @@ export function useDerivedMintInfo(
currencies: { [field in Field]?: Currency }
pair?: Pair | null
pairState: PairState
currencyBalances: { [field in Field]?: CurrencyAmount }
parsedAmounts: { [field in Field]?: CurrencyAmount }
price?: Price
currencyBalances: { [field in Field]?: CurrencyAmount<Currency> }
parsedAmounts: { [field in Field]?: CurrencyAmount<Currency> }
price?: Price<Currency, Currency>
noLiquidity?: boolean
liquidityMinted?: TokenAmount
liquidityMinted?: CurrencyAmount<Token>
poolTokenPercentage?: Percent
error?: string
} {
@@ -83,21 +83,24 @@ export function useDerivedMintInfo(
const totalSupply = useTotalSupply(pair?.liquidityToken)
const noLiquidity: boolean =
pairState === PairState.NOT_EXISTS || Boolean(totalSupply && JSBI.equal(totalSupply.raw, ZERO))
pairState === PairState.NOT_EXISTS || Boolean(totalSupply && JSBI.equal(totalSupply.quotient, ZERO))
// balances
const balances = useCurrencyBalances(account ?? undefined, [
currencies[Field.CURRENCY_A],
currencies[Field.CURRENCY_B],
])
const currencyBalances: { [field in Field]?: CurrencyAmount } = {
const currencyBalances: { [field in Field]?: CurrencyAmount<Currency> } = {
[Field.CURRENCY_A]: balances[0],
[Field.CURRENCY_B]: balances[1],
}
// amounts
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField])
const dependentAmount: CurrencyAmount | undefined = useMemo(() => {
const independentAmount: CurrencyAmount<Currency> | undefined = tryParseAmount(
typedValue,
currencies[independentField]
)
const dependentAmount: CurrencyAmount<Currency> | undefined = useMemo(() => {
if (noLiquidity) {
if (otherTypedValue && currencies[dependentField]) {
return tryParseAmount(otherTypedValue, currencies[dependentField])
@@ -113,7 +116,7 @@ export function useDerivedMintInfo(
dependentField === Field.CURRENCY_B
? pair.priceOf(tokenA).quote(wrappedIndependentAmount)
: pair.priceOf(tokenB).quote(wrappedIndependentAmount)
return dependentCurrency === ETHER ? CurrencyAmount.ether(dependentTokenAmount.raw) : dependentTokenAmount
return dependentCurrency?.isEther ? CurrencyAmount.ether(dependentTokenAmount.quotient) : dependentTokenAmount
}
return undefined
} else {
@@ -121,7 +124,7 @@ export function useDerivedMintInfo(
}
}, [noLiquidity, otherTypedValue, currencies, dependentField, independentAmount, currencyA, chainId, currencyB, pair])
const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = useMemo(() => {
const parsedAmounts: { [field in Field]: CurrencyAmount<Currency> | undefined } = useMemo(() => {
return {
[Field.CURRENCY_A]: independentField === Field.CURRENCY_A ? independentAmount : dependentAmount,
[Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount,
@@ -132,7 +135,12 @@ export function useDerivedMintInfo(
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 new Price(
currencyAAmount.currency,
currencyBAmount.currency,
currencyAAmount.quotient,
currencyBAmount.quotient
)
}
return undefined
} else {
@@ -157,7 +165,7 @@ export function useDerivedMintInfo(
const poolTokenPercentage = useMemo(() => {
if (liquidityMinted && totalSupply) {
return new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
return new Percent(liquidityMinted.quotient, totalSupply.add(liquidityMinted).quotient)
} else {
return undefined
}

View File

@@ -10,8 +10,9 @@ import {
TickMath,
tickToPrice,
TICK_SPACINGS,
encodeSqrtRatioX96,
} from '@uniswap/v3-sdk/dist/'
import { Currency, CurrencyAmount, ETHER, Price, Rounding } from '@uniswap/sdk-core'
import { Currency, Token, CurrencyAmount, currencyEquals, Price, Rounding } from '@uniswap/sdk-core'
import { useCallback, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useActiveWeb3React } from '../../../hooks'
@@ -93,14 +94,14 @@ export function useV3DerivedMintInfo(
pool?: Pool | null
poolState: PoolState
ticks: { [bound in Bound]?: number | undefined }
price?: Price
price?: Price<Token, Token>
pricesAtTicks: {
[bound in Bound]?: Price | undefined
[bound in Bound]?: Price<Token, Token> | undefined
}
currencies: { [field in Field]?: Currency }
currencyBalances: { [field in Field]?: CurrencyAmount }
currencyBalances: { [field in Field]?: CurrencyAmount<Currency> }
dependentField: Field
parsedAmounts: { [field in Field]?: CurrencyAmount }
parsedAmounts: { [field in Field]?: CurrencyAmount<Currency> }
position: Position | undefined
noLiquidity?: boolean
errorMessage?: string
@@ -153,7 +154,7 @@ export function useV3DerivedMintInfo(
currencies[Field.CURRENCY_A],
currencies[Field.CURRENCY_B],
])
const currencyBalances: { [field in Field]?: CurrencyAmount } = {
const currencyBalances: { [field in Field]?: CurrencyAmount<Currency> } = {
[Field.CURRENCY_A]: balances[0],
[Field.CURRENCY_B]: balances[1],
}
@@ -166,7 +167,7 @@ export function useV3DerivedMintInfo(
const invertPrice = Boolean(baseToken && token0 && !baseToken.equals(token0))
// always returns the price with 0 as base token
const price = useMemo(() => {
const price: Price<Token, Token> | undefined = useMemo(() => {
// if no liquidity use typed value
if (noLiquidity) {
const parsedQuoteAmount = tryParseAmount(startPriceTypedValue, invertPrice ? token0 : token1)
@@ -174,7 +175,12 @@ export function useV3DerivedMintInfo(
const baseAmount = tryParseAmount('1', invertPrice ? token1 : token0)
const price =
baseAmount && parsedQuoteAmount
? new Price(baseAmount.currency, parsedQuoteAmount.currency, baseAmount.raw, parsedQuoteAmount.raw)
? new Price(
baseAmount.currency,
parsedQuoteAmount.currency,
baseAmount.quotient,
parsedQuoteAmount.quotient
)
: undefined
return (invertPrice ? price?.invert() : price) ?? undefined
}
@@ -185,16 +191,29 @@ export function useV3DerivedMintInfo(
}
}, [noLiquidity, startPriceTypedValue, invertPrice, token1, token0, pool])
// check for invalid price input (converts to invalid ratio)
const invalidPrice = useMemo(() => {
const sqrtRatioX96 = price ? encodeSqrtRatioX96(price.numerator, price.denominator) : undefined
const invalid =
price &&
sqrtRatioX96 &&
!(
JSBI.greaterThanOrEqual(sqrtRatioX96, TickMath.MIN_SQRT_RATIO) &&
JSBI.lessThan(sqrtRatioX96, TickMath.MAX_SQRT_RATIO)
)
return invalid
}, [price])
// used for ratio calculation when pool not initialized
const mockPool = useMemo(() => {
if (tokenA && tokenB && feeAmount && price) {
if (tokenA && tokenB && feeAmount && price && !invalidPrice) {
const currentTick = priceToClosestTick(price)
const currentSqrt = TickMath.getSqrtRatioAtTick(currentTick)
return new Pool(tokenA, tokenB, feeAmount, currentSqrt, JSBI.BigInt(0), currentTick, [])
} else {
return undefined
}
}, [feeAmount, price, tokenA, tokenB])
}, [feeAmount, invalidPrice, price, tokenA, tokenB])
// if pool exists use it, if not use the mock pool
const poolForPosition: Pool | undefined = pool ?? mockPool
@@ -240,9 +259,12 @@ export function useV3DerivedMintInfo(
)
// amounts
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField])
const independentAmount: CurrencyAmount<Currency> | undefined = tryParseAmount(
typedValue,
currencies[independentField]
)
const dependentAmount: CurrencyAmount | undefined = useMemo(() => {
const dependentAmount: CurrencyAmount<Currency> | undefined = useMemo(() => {
// we wrap the currencies just to get the price in terms of the other token
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
const dependentCurrency = dependentField === Field.CURRENCY_B ? currencyB : currencyA
@@ -258,24 +280,24 @@ export function useV3DerivedMintInfo(
return undefined
}
const position: Position | undefined = wrappedIndependentAmount.token.equals(poolForPosition.token0)
const position: Position | undefined = currencyEquals(wrappedIndependentAmount.currency, poolForPosition.token0)
? Position.fromAmount0({
pool: poolForPosition,
tickLower,
tickUpper,
amount0: independentAmount.raw,
amount0: independentAmount.quotient,
})
: Position.fromAmount1({
pool: poolForPosition,
tickLower,
tickUpper,
amount1: independentAmount.raw,
amount1: independentAmount.quotient,
})
const dependentTokenAmount = wrappedIndependentAmount.token.equals(poolForPosition.token0)
const dependentTokenAmount = currencyEquals(wrappedIndependentAmount.currency, poolForPosition.token0)
? position.amount1
: position.amount0
return dependentCurrency === ETHER ? CurrencyAmount.ether(dependentTokenAmount.raw) : dependentTokenAmount
return dependentCurrency?.isEther ? CurrencyAmount.ether(dependentTokenAmount.quotient) : dependentTokenAmount
}
return undefined
@@ -292,7 +314,7 @@ export function useV3DerivedMintInfo(
invalidRange,
])
const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = useMemo(() => {
const parsedAmounts: { [field in Field]: CurrencyAmount<Currency> | undefined } = useMemo(() => {
return {
[Field.CURRENCY_A]: independentField === Field.CURRENCY_A ? independentAmount : dependentAmount,
[Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount,
@@ -336,10 +358,10 @@ export function useV3DerivedMintInfo(
// mark as 0 if disbaled because out of range
const amount0 = !deposit0Disabled
? parsedAmounts?.[tokenA.equals(poolForPosition.token0) ? Field.CURRENCY_A : Field.CURRENCY_B]?.raw
? parsedAmounts?.[tokenA.equals(poolForPosition.token0) ? Field.CURRENCY_A : Field.CURRENCY_B]?.quotient
: BIG_INT_ZERO
const amount1 = !deposit1Disabled
? parsedAmounts?.[tokenA.equals(poolForPosition.token0) ? Field.CURRENCY_B : Field.CURRENCY_A]?.raw
? parsedAmounts?.[tokenA.equals(poolForPosition.token0) ? Field.CURRENCY_B : Field.CURRENCY_A]?.quotient
: BIG_INT_ZERO
if (amount0 !== undefined && amount1 !== undefined) {
@@ -374,6 +396,10 @@ export function useV3DerivedMintInfo(
errorMessage = errorMessage ?? 'Invalid pair'
}
if (invalidPrice) {
errorMessage = errorMessage ?? 'Invalid price input'
}
if (
(!parsedAmounts[Field.CURRENCY_A] && !depositADisabled) ||
(!parsedAmounts[Field.CURRENCY_B] && !depositBDisabled)

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