Commit ca60caf6 authored by eddie's avatar eddie Committed by GitHub

feat: use Swap Component on TDP (#6332)

* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button
parent 252acef1
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
import { getClassContainsSelector, getTestSelector } from '../utils'
const UNI_GOERLI = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
const WETH_GOERLI = '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6'
describe('swap widget integration tests', () => {
const verifyInputToken = (inputText: string) => {
cy.get(getClassContainsSelector('TokenButtonRow')).first().contains(inputText)
}
const verifyOutputToken = (outputText: string) => {
cy.get(getClassContainsSelector('TokenButtonRow')).last().contains(outputText)
}
const selectOutputAndSwitch = (outputText: string) => {
// open token selector...
cy.contains('Select token').click()
// select token...
cy.contains(outputText).click({ force: true })
cy.get('body')
.then(($body) => {
if ($body.find(getTestSelector('TokenSafetyWrapper')).length) {
return 'I understand'
}
return 'You pay' // Just click on a random element as a no-op
})
.then((selector) => {
cy.contains(selector).click()
})
// token selector should close...
cy.contains('Search name or paste address').should('not.exist')
cy.get(getClassContainsSelector('ReverseButton')).first().click()
}
describe('widget on swap page', () => {
beforeEach(() => {
cy.viewport(1200, 800)
})
it('should have the correct default input/output and token selection should work', () => {
cy.visit('/swap', { featureFlags: [FeatureFlag.swapWidget] }).then(() => {
cy.wait('@eth_blockNumber')
verifyInputToken('ETH')
verifyOutputToken('Select token')
selectOutputAndSwitch('WETH')
verifyInputToken('WETH')
verifyOutputToken('ETH')
})
})
it('should have the correct default input from URL params ', () => {
cy.visit(`/swap?inputCurrency=${WETH_GOERLI}`, {
featureFlags: [FeatureFlag.swapWidget],
}).then(() => {
cy.wait('@eth_blockNumber')
})
verifyInputToken('WETH')
verifyOutputToken('Select token')
selectOutputAndSwitch('Ether')
verifyInputToken('ETH')
verifyOutputToken('WETH')
})
it('should have the correct default output from URL params ', () => {
cy.visit(`/swap?outputCurrency=${WETH_GOERLI}`, {
featureFlags: [FeatureFlag.swapWidget],
}).then(() => {
cy.wait('@eth_blockNumber')
})
verifyInputToken('Select token')
verifyOutputToken('WETH')
cy.get(getClassContainsSelector('ReverseButton')).first().click()
verifyInputToken('WETH')
verifyOutputToken('Select token')
selectOutputAndSwitch('Ether')
verifyInputToken('ETH')
verifyOutputToken('WETH')
})
})
describe('widget on Token Detail Page', () => {
beforeEach(() => {
cy.viewport(1200, 800)
cy.visit(`/tokens/ethereum/${UNI_GOERLI}`, { featureFlags: [FeatureFlag.swapWidget] }).then(() => {
cy.wait('@eth_blockNumber')
})
})
it('should have the expected output for a tokens detail page', () => {
verifyOutputToken('UNI')
cy.contains('Connect to Ethereum').should('exist')
})
})
})
import { USDC_MAINNET } from '../../src/constants/tokens' import { WETH9 } from '@uniswap/sdk-core'
import { UNI as UNI_MAINNET, USDC_MAINNET } from '../../src/constants/tokens'
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
import { WETH_GOERLI } from '../fixtures/constants' import { WETH_GOERLI } from '../fixtures/constants'
import { getTestSelector } from '../utils' import { getTestSelector } from '../utils'
...@@ -19,9 +22,9 @@ describe('Swap', () => { ...@@ -19,9 +22,9 @@ describe('Swap', () => {
} }
} }
const selectOutput = (tokenSymbol: string) => { const selectToken = (tokenSymbol: string, field: 'input' | 'output') => {
// open token selector... // open token selector...
cy.contains('Select token').click() cy.get(`#swap-currency-${field} .open-currency-select-button`).click()
// select token... // select token...
cy.contains(tokenSymbol).click() cy.contains(tokenSymbol).click()
...@@ -43,6 +46,7 @@ describe('Swap', () => { ...@@ -43,6 +46,7 @@ describe('Swap', () => {
cy.contains('Search name or paste address').should('not.exist') cy.contains('Search name or paste address').should('not.exist')
} }
describe('Swap on main page', () => {
before(() => { before(() => {
cy.visit('/swap', { ethereum: 'hardhat' }) cy.visit('/swap', { ethereum: 'hardhat' })
}) })
...@@ -74,44 +78,12 @@ describe('Swap', () => { ...@@ -74,44 +78,12 @@ describe('Swap', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.0').should('have.value', '0.0') cy.get('#swap-currency-output .token-amount-input').clear().type('0.0').should('have.value', '0.0')
}) })
it('can swap ETH for USDC', () => {
const TOKEN_ADDRESS = USDC_MAINNET.address
const BALANCE_INCREMENT = 1
cy.hardhat().then((hardhat) => {
cy.then(() => hardhat.getBalance(hardhat.wallet.address, USDC_MAINNET))
.then((balance) => Number(balance.toFixed(1)))
.then((initialBalance) => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get(getTestSelector('token-search-input')).clear().type(TOKEN_ADDRESS)
cy.contains('USDC').click()
cy.get('#swap-currency-output .token-amount-input').clear().type(BALANCE_INCREMENT.toString())
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').click()
cy.get(getTestSelector('dismiss-tx-confirmation')).click()
cy.then(() => hardhat.provider.send('hardhat_mine', ['0x1', '0xc'])).then(() => {
// ui check
cy.get('#swap-currency-output [data-testid="balance-text"]').should(
'have.text',
`Balance: ${initialBalance + BALANCE_INCREMENT}`
)
// chain state check
cy.then(() => hardhat.getBalance(hardhat.wallet.address, USDC_MAINNET))
.then((balance) => Number(balance.toFixed(1)))
.should('eq', initialBalance + BALANCE_INCREMENT)
})
})
})
})
it('should have the correct default input/output and token selection should work', () => { it('should have the correct default input/output and token selection should work', () => {
cy.visit('/swap') cy.visit('/swap')
verifyToken('input', 'ETH') verifyToken('input', 'ETH')
verifyToken('output', null) verifyToken('output', null)
selectOutput('WETH') selectToken('WETH', 'output')
cy.get(getTestSelector('swap-currency-button')).first().click() cy.get(getTestSelector('swap-currency-button')).first().click()
verifyToken('input', 'WETH') verifyToken('input', 'WETH')
...@@ -124,7 +96,7 @@ describe('Swap', () => { ...@@ -124,7 +96,7 @@ describe('Swap', () => {
verifyToken('input', 'WETH') verifyToken('input', 'WETH')
verifyToken('output', null) verifyToken('output', null)
selectOutput('Ether') selectToken('Ether', 'output')
cy.get(getTestSelector('swap-currency-button')).first().click() cy.get(getTestSelector('swap-currency-button')).first().click()
verifyToken('input', 'ETH') verifyToken('input', 'ETH')
...@@ -141,7 +113,7 @@ describe('Swap', () => { ...@@ -141,7 +113,7 @@ describe('Swap', () => {
verifyToken('input', 'WETH') verifyToken('input', 'WETH')
verifyToken('output', null) verifyToken('output', null)
selectOutput('Ether') selectToken('Ether', 'output')
cy.get(getTestSelector('swap-currency-button')).first().click() cy.get(getTestSelector('swap-currency-button')).first().click()
verifyToken('input', 'ETH') verifyToken('input', 'ETH')
...@@ -150,30 +122,11 @@ describe('Swap', () => { ...@@ -150,30 +122,11 @@ describe('Swap', () => {
it('ETH to wETH is same value (wrapped swaps have no price impact)', () => { it('ETH to wETH is same value (wrapped swaps have no price impact)', () => {
cy.visit('/swap') cy.visit('/swap')
selectOutput('WETH') selectToken('WETH', 'output')
cy.get('#swap-currency-input .token-amount-input').clear().type('0.01') cy.get('#swap-currency-input .token-amount-input').clear().type('0.01')
cy.get('#swap-currency-output .token-amount-input').should('have.value', '0.01') cy.get('#swap-currency-output .token-amount-input').should('have.value', '0.01')
}) })
it('should render and dismiss the wallet rejection modal', () => {
cy.visit('/swap', { ethereum: 'hardhat' })
.hardhat()
.then((hardhat) => {
cy.stub(hardhat.wallet, 'sendTransaction').rejects(new Error('user cancelled'))
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get(getTestSelector('token-search-input')).clear().type(USDC_MAINNET.address)
cy.contains('USDC').click()
cy.get('#swap-currency-output .token-amount-input').clear().type('1')
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').click()
cy.contains('Transaction rejected').should('exist')
cy.contains('Dismiss').click()
cy.contains('Transaction rejected').should('not.exist')
})
})
it('Opens and closes the settings menu', () => { it('Opens and closes the settings menu', () => {
cy.visit('/swap') cy.visit('/swap')
cy.contains('Settings').should('not.exist') cy.contains('Settings').should('not.exist')
...@@ -194,4 +147,103 @@ describe('Swap', () => { ...@@ -194,4 +147,103 @@ describe('Swap', () => {
cy.get('#swap-currency-input .token-amount-input').should('have.value', '') cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '') cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
}) })
it('can swap ETH for USDC', () => {
cy.visit('/swap', { ethereum: 'hardhat' })
const TOKEN_ADDRESS = USDC_MAINNET.address
const BALANCE_INCREMENT = 1
cy.hardhat().then((hardhat) => {
cy.then(() => hardhat.getBalance(hardhat.wallet.address, USDC_MAINNET))
.then((balance) => Number(balance.toFixed(1)))
.then((initialBalance) => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get(getTestSelector('token-search-input')).clear().type(TOKEN_ADDRESS)
cy.contains('USDC').click()
cy.get('#swap-currency-output .token-amount-input').clear().type(BALANCE_INCREMENT.toString())
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').click()
cy.get(getTestSelector('dismiss-tx-confirmation')).click()
cy.then(() => hardhat.provider.send('hardhat_mine', ['0x1', '0xc'])).then(() => {
// ui check
cy.get('#swap-currency-output [data-testid="balance-text"]').should(
'have.text',
`Balance: ${initialBalance + BALANCE_INCREMENT}`
)
// chain state check
cy.then(() => hardhat.getBalance(hardhat.wallet.address, USDC_MAINNET))
.then((balance) => Number(balance.toFixed(1)))
.should('eq', initialBalance + BALANCE_INCREMENT)
})
})
})
})
})
describe('Swap on Token Detail Page', () => {
beforeEach(() => {
// On mobile widths, we just link back to /swap instead of rendering the swap component.
cy.viewport(1200, 800)
cy.visit(`/tokens/ethereum/${UNI_MAINNET[1].address}`, {
ethereum: 'hardhat',
featureFlags: [FeatureFlag.removeWidget],
}).then(() => {
cy.wait('@eth_blockNumber')
cy.scrollTo('top')
})
})
it('should have the expected output for a tokens detail page', () => {
verifyAmount('input', '')
verifyToken('input', null)
verifyAmount('output', null)
verifyToken('output', 'UNI')
})
it('should automatically navigate to the new TDP', () => {
selectToken('WETH', 'output')
cy.url().should('include', `${WETH9[1].address}`)
cy.url().should('not.include', `${UNI_MAINNET[1].address}`)
})
it('should not share swap state with the main swap page', () => {
verifyToken('output', 'UNI')
selectToken('WETH', 'input')
cy.visit('/swap', { featureFlags: [FeatureFlag.removeWidget] })
cy.contains('UNI').should('not.exist')
cy.contains('WETH').should('not.exist')
})
it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.001').should('have.value', '0.001')
})
it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0').should('have.value', '0.0')
})
it('invalid swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('\\').should('have.value', '')
})
it('can enter an amount into output', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.001').should('have.value', '0.001')
})
it('zero output amount', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.0').should('have.value', '0.0')
})
it('should show a L2 token even if the user is connected to a different network', () => {
cy.visit('/tokens', { ethereum: 'hardhat', featureFlags: [FeatureFlag.removeWidget] })
cy.get(getTestSelector('tokens-network-filter-selected')).click()
cy.get(getTestSelector('tokens-network-filter-option-arbitrum')).click()
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Arbitrum')
cy.get(getTestSelector('token-table-row-ARB')).click()
verifyToken('output', 'ARB')
cy.contains('Connect to Arbitrum').should('exist')
})
})
}) })
import { getClassContainsSelector, getTestSelector } from '../utils' import { getTestSelector } from '../utils'
const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984' const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
describe('Token details', () => { describe('Token details', () => {
before(() => { beforeEach(() => {
cy.visit('/') cy.viewport(1440, 900)
}) })
it('Uniswap token should have all information populated', () => { it('Uniswap token should have all information populated', () => {
...@@ -40,9 +40,6 @@ describe('Token details', () => { ...@@ -40,9 +40,6 @@ describe('Token details', () => {
// Contract address should be displayed // Contract address should be displayed
cy.contains(UNI_ADDRESS).should('exist') cy.contains(UNI_ADDRESS).should('exist')
// Swap widget should have this token pre-selected as the “destination” token
cy.get(getTestSelector('token-select')).should('include.text', 'UNI')
}) })
it('token with warning and low trading volume should have all information populated', () => { it('token with warning and low trading volume should have all information populated', () => {
...@@ -81,36 +78,9 @@ describe('Token details', () => { ...@@ -81,36 +78,9 @@ describe('Token details', () => {
// Contract address should be displayed // Contract address should be displayed
cy.contains('0xa71d0588EAf47f12B13cF8eC750430d21DF04974').should('exist') cy.contains('0xa71d0588EAf47f12B13cF8eC750430d21DF04974').should('exist')
// Swap widget should have this token pre-selected as the “destination” token
cy.get(getTestSelector('token-select')).should('include.text', 'QOM')
// Warning label should show if relevant ([spec](https://www.notion.so/3f7fce6f93694be08a94a6984d50298e)) // Warning label should show if relevant ([spec](https://www.notion.so/3f7fce6f93694be08a94a6984d50298e))
cy.get('[data-cy="token-safety-message"]') cy.get('[data-cy="token-safety-message"]')
.should('include.text', 'Warning') .should('include.text', 'Warning')
.and('include.text', "This token isn't traded on leading U.S. centralized exchanges") .and('include.text', "This token isn't traded on leading U.S. centralized exchanges")
}) })
describe('Swap on Token Detail Page', () => {
const verifyOutputToken = (outputText: string) => {
cy.get(getClassContainsSelector('TokenButtonRow')).last().contains(outputText)
}
beforeEach(() => {
// On mobile widths, we just link back to /swap instead of rendering the swap component.
cy.viewport(1200, 800)
cy.visit(`/tokens/goerli/${UNI_ADDRESS}`).then(() => {
cy.wait('@eth_blockNumber')
})
})
it('should have the expected output for a tokens detail page', () => {
verifyOutputToken('UNI')
})
it('should not share swap state with the main swap page', () => {
verifyOutputToken('UNI')
cy.visit('/swap')
cy.contains('UNI').should('not.exist')
})
})
}) })
export const getTestSelector = (selectorId: string) => `[data-testid=${selectorId}]` export const getTestSelector = (selectorId: string) => `[data-testid=${selectorId}]`
export const getTestSelectorStartsWith = (selectorId: string) => `[data-testid^=${selectorId}]` export const getTestSelectorStartsWith = (selectorId: string) => `[data-testid^=${selectorId}]`
export const getClassContainsSelector = (selectorId: string) => `[class*=${selectorId}]`
...@@ -204,6 +204,7 @@ interface SwapCurrencyInputPanelProps { ...@@ -204,6 +204,7 @@ interface SwapCurrencyInputPanelProps {
renderBalance?: (amount: CurrencyAmount<Currency>) => ReactNode renderBalance?: (amount: CurrencyAmount<Currency>) => ReactNode
locked?: boolean locked?: boolean
loading?: boolean loading?: boolean
disabled?: boolean
} }
export default function SwapCurrencyInputPanel({ export default function SwapCurrencyInputPanel({
...@@ -226,6 +227,7 @@ export default function SwapCurrencyInputPanel({ ...@@ -226,6 +227,7 @@ export default function SwapCurrencyInputPanel({
hideInput = false, hideInput = false,
locked = false, locked = false,
loading = false, loading = false,
disabled = false,
...rest ...rest
}: SwapCurrencyInputPanelProps) { }: SwapCurrencyInputPanelProps) {
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false)
...@@ -258,13 +260,13 @@ export default function SwapCurrencyInputPanel({ ...@@ -258,13 +260,13 @@ export default function SwapCurrencyInputPanel({
className="token-amount-input" className="token-amount-input"
value={value} value={value}
onUserInput={onUserInput} onUserInput={onUserInput}
disabled={!chainAllowed} disabled={!chainAllowed || disabled}
$loading={loading} $loading={loading}
/> />
)} )}
<CurrencySelect <CurrencySelect
disabled={!chainAllowed} disabled={!chainAllowed || disabled}
visible={currency !== undefined} visible={currency !== undefined}
selected={!!currency} selected={!!currency}
hideInput={hideInput} hideInput={hideInput}
......
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags' import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { DetailsV2Variant, useDetailsV2Flag } from 'featureFlags/flags/nftDetails' import { DetailsV2Variant, useDetailsV2Flag } from 'featureFlags/flags/nftDetails'
import { useWidgetRemovalFlag, WidgetRemovalVariant } from 'featureFlags/flags/removeWidgetTdp'
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget' import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc' import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { useUpdateAtom } from 'jotai/utils' import { useUpdateAtom } from 'jotai/utils'
...@@ -214,6 +215,12 @@ export default function FeatureFlagModal() { ...@@ -214,6 +215,12 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.detailsV2} featureFlag={FeatureFlag.detailsV2}
label="Use the new details page for nfts" label="Use the new details page for nfts"
/> />
<FeatureFlagOption
variant={WidgetRemovalVariant}
value={useWidgetRemovalFlag()}
featureFlag={FeatureFlag.removeWidget}
label="Swap Component on TDP"
/>
<FeatureFlagGroup name="Debug"> <FeatureFlagGroup name="Debug">
<FeatureFlagOption <FeatureFlagOption
variant={TraceJsonRpcVariant} variant={TraceJsonRpcVariant}
......
...@@ -85,7 +85,7 @@ export function CurrencySearch({ ...@@ -85,7 +85,7 @@ export function CurrencySearch({
} }
}, [isAddressSearch]) }, [isAddressSearch])
const defaultTokens = useDefaultActiveTokens() const defaultTokens = useDefaultActiveTokens(chainId)
const filteredTokens: Token[] = useMemo(() => { const filteredTokens: Token[] = useMemo(() => {
return Object.values(defaultTokens).filter(getTokenFilter(debouncedQuery)) return Object.values(defaultTokens).filter(getTokenFilter(debouncedQuery))
}, [defaultTokens, debouncedQuery]) }, [defaultTokens, debouncedQuery])
...@@ -123,7 +123,7 @@ export function CurrencySearch({ ...@@ -123,7 +123,7 @@ export function CurrencySearch({
const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens) const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)
const native = useNativeCurrency() const native = useNativeCurrency(chainId)
const wrapped = native.wrapped const wrapped = native.wrapped
const searchCurrencies: Currency[] = useMemo(() => { const searchCurrencies: Currency[] = useMemo(() => {
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Trace } from '@uniswap/analytics' import { Trace } from '@uniswap/analytics'
import { InterfacePageName } from '@uniswap/analytics-events' import { InterfacePageName } from '@uniswap/analytics-events'
import { Currency, Field } from '@uniswap/widgets' import { Currency } from '@uniswap/widgets'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import CurrencyLogo from 'components/Logo/CurrencyLogo' import CurrencyLogo from 'components/Logo/CurrencyLogo'
import { AboutSection } from 'components/Tokens/TokenDetails/About' import { AboutSection } from 'components/Tokens/TokenDetails/About'
...@@ -26,6 +26,7 @@ import Widget from 'components/Widget' ...@@ -26,6 +26,7 @@ import Widget from 'components/Widget'
import { SwapTokens } from 'components/Widget/inputs' import { SwapTokens } from 'components/Widget/inputs'
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens' import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
import { checkWarning } from 'constants/tokenSafety' import { checkWarning } from 'constants/tokenSafety'
import { useWidgetRemovalEnabled } from 'featureFlags/flags/removeWidgetTdp'
import { TokenPriceQuery } from 'graphql/data/__generated__/types-and-hooks' import { TokenPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token' import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token'
import { QueryToken } from 'graphql/data/Token' import { QueryToken } from 'graphql/data/Token'
...@@ -34,11 +35,15 @@ import { useIsUserAddedTokenOnChain } from 'hooks/Tokens' ...@@ -34,11 +35,15 @@ import { useIsUserAddedTokenOnChain } from 'hooks/Tokens'
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch' import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency' import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency'
import { getTokenAddress } from 'lib/utils/analytics' import { getTokenAddress } from 'lib/utils/analytics'
import { Swap } from 'pages/Swap'
import { useCallback, useMemo, useState, useTransition } from 'react' import { useCallback, useMemo, useState, useTransition } from 'react'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { Field } from 'state/swap/actions'
import { SwapState } from 'state/swap/reducer'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { isAddress } from 'utils' import { isAddress } from 'utils'
import { addressesAreEquivalent } from 'utils/addressesAreEquivalent'
import { OnChangeTimePeriod } from './ChartSection' import { OnChangeTimePeriod } from './ChartSection'
import InvalidTokenDetails from './InvalidTokenDetails' import InvalidTokenDetails from './InvalidTokenDetails'
...@@ -111,6 +116,7 @@ export default function TokenDetails({ ...@@ -111,6 +116,7 @@ export default function TokenDetails({
[urlAddress] [urlAddress]
) )
const { chainId: connectedChainId } = useWeb3React()
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain] const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const tokenQueryData = tokenQuery.token const tokenQueryData = tokenQuery.token
...@@ -124,11 +130,12 @@ export default function TokenDetails({ ...@@ -124,11 +130,12 @@ export default function TokenDetails({
) )
const { token: detailedToken, didFetchFromChain } = useRelevantToken(address, pageChainId, tokenQueryData) const { token: detailedToken, didFetchFromChain } = useRelevantToken(address, pageChainId, tokenQueryData)
const { token: inputToken } = useRelevantToken(inputTokenAddress, pageChainId, undefined) const { token: widgetInputToken } = useRelevantToken(inputTokenAddress, pageChainId, undefined)
const tokenWarning = address ? checkWarning(address) : null const tokenWarning = address ? checkWarning(address) : null
const isBlockedToken = tokenWarning?.canProceed === false const isBlockedToken = tokenWarning?.canProceed === false
const navigate = useNavigate() const navigate = useNavigate()
const widgetRemovalEnabled = useWidgetRemovalEnabled()
// Wrapping navigate in a transition prevents Suspense from unnecessarily showing fallbacks again. // Wrapping navigate in a transition prevents Suspense from unnecessarily showing fallbacks again.
const [isPending, startTokenTransition] = useTransition() const [isPending, startTokenTransition] = useTransition()
...@@ -145,7 +152,6 @@ export default function TokenDetails({ ...@@ -145,7 +152,6 @@ export default function TokenDetails({
[address, crossChainMap, didFetchFromChain, navigate, detailedToken?.isNative] [address, crossChainMap, didFetchFromChain, navigate, detailedToken?.isNative]
) )
useOnGlobalChainSwitch(navigateToTokenForChain) useOnGlobalChainSwitch(navigateToTokenForChain)
const navigateToWidgetSelectedToken = useCallback( const navigateToWidgetSelectedToken = useCallback(
(tokens: SwapTokens) => { (tokens: SwapTokens) => {
const newDefaultToken = tokens[Field.OUTPUT] ?? tokens.default const newDefaultToken = tokens[Field.OUTPUT] ?? tokens.default
...@@ -163,11 +169,39 @@ export default function TokenDetails({ ...@@ -163,11 +169,39 @@ export default function TokenDetails({
[chain, navigate] [chain, navigate]
) )
const handleCurrencyChange = useCallback(
(tokens: Pick<SwapState, Field.INPUT | Field.OUTPUT>) => {
if (
addressesAreEquivalent(tokens[Field.INPUT]?.currencyId, address) ||
addressesAreEquivalent(tokens[Field.OUTPUT]?.currencyId, address)
) {
return
}
const newDefaultTokenID = tokens[Field.OUTPUT]?.currencyId ?? tokens[Field.INPUT]?.currencyId
startTokenTransition(() =>
navigate(
getTokenDetailsURL({
// The function falls back to "NATIVE" if the address is null
address: newDefaultTokenID === 'ETH' ? null : newDefaultTokenID,
chain,
inputAddress:
// If only one token was selected before we navigate, then it was the default token and it's being replaced.
// On the new page, the *new* default token becomes the output, and we don't have another option to set as the input token.
tokens[Field.INPUT] && tokens[Field.INPUT]?.currencyId !== newDefaultTokenID
? tokens[Field.INPUT]?.currencyId
: null,
})
)
)
},
[address, chain, navigate]
)
const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike<boolean>) => void }>() const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike<boolean>) => void }>()
const [openTokenSafetyModal, setOpenTokenSafetyModal] = useState(false) const [openTokenSafetyModal, setOpenTokenSafetyModal] = useState(false)
// Show token safety modal if Swap-reviewing a warning token, at all times if the current token is blocked
const shouldShowSpeedbump = !useIsUserAddedTokenOnChain(address, pageChainId) && tokenWarning !== null const shouldShowSpeedbump = !useIsUserAddedTokenOnChain(address, pageChainId) && tokenWarning !== null
const onReviewSwapClick = useCallback( const onReviewSwapClick = useCallback(
() => new Promise<boolean>((resolve) => (shouldShowSpeedbump ? setContinueSwap({ resolve }) : resolve(true))), () => new Promise<boolean>((resolve) => (shouldShowSpeedbump ? setContinueSwap({ resolve }) : resolve(true))),
...@@ -234,14 +268,26 @@ export default function TokenDetails({ ...@@ -234,14 +268,26 @@ export default function TokenDetails({
<RightPanel onClick={() => isBlockedToken && setOpenTokenSafetyModal(true)}> <RightPanel onClick={() => isBlockedToken && setOpenTokenSafetyModal(true)}>
<div style={{ pointerEvents: isBlockedToken ? 'none' : 'auto' }}> <div style={{ pointerEvents: isBlockedToken ? 'none' : 'auto' }}>
{widgetRemovalEnabled ? (
<Swap
chainId={pageChainId}
prefilledState={{
[Field.INPUT]: { currencyId: inputTokenAddress },
[Field.OUTPUT]: { currencyId: address === NATIVE_CHAIN_ID ? 'ETH' : address },
}}
onCurrencyChange={handleCurrencyChange}
disableTokenInputs={pageChainId !== connectedChainId}
/>
) : (
<Widget <Widget
defaultTokens={{ defaultTokens={{
[Field.INPUT]: inputToken ?? undefined, [Field.INPUT]: widgetInputToken ?? undefined,
default: detailedToken ?? undefined, default: detailedToken ?? undefined,
}} }}
onDefaultTokenChange={navigateToWidgetSelectedToken} onDefaultTokenChange={navigateToWidgetSelectedToken}
onReviewSwapClick={onReviewSwapClick} onReviewSwapClick={onReviewSwapClick}
/> />
)}
</div> </div>
{tokenWarning && <TokenSafetyMessage tokenAddress={address} warning={tokenWarning} />} {tokenWarning && <TokenSafetyMessage tokenAddress={address} warning={tokenWarning} />}
{detailedToken && <BalanceSummary token={detailedToken} />} {detailedToken && <BalanceSummary token={detailedToken} />}
......
...@@ -51,6 +51,8 @@ interface WidgetProps { ...@@ -51,6 +51,8 @@ interface WidgetProps {
onReviewSwapClick?: OnReviewSwapClick onReviewSwapClick?: OnReviewSwapClick
} }
// TODO: Remove this component once the TDP is fully migrated to the swap component.
// eslint-disable-next-line import/no-unused-modules
export default function Widget({ export default function Widget({
defaultTokens, defaultTokens,
width = DEFAULT_WIDGET_WIDTH, width = DEFAULT_WIDGET_WIDTH,
......
...@@ -53,7 +53,7 @@ export function AdvancedSwapDetails({ ...@@ -53,7 +53,7 @@ export function AdvancedSwapDetails({
}: AdvancedSwapDetailsProps) { }: AdvancedSwapDetailsProps) {
const theme = useTheme() const theme = useTheme()
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const nativeCurrency = useNativeCurrency() const nativeCurrency = useNativeCurrency(chainId)
const { expectedOutputAmount, priceImpact } = useMemo(() => { const { expectedOutputAmount, priceImpact } = useMemo(() => {
return { return {
......
...@@ -515,6 +515,13 @@ export function nativeOnChain(chainId: number): NativeCurrency | Token { ...@@ -515,6 +515,13 @@ export function nativeOnChain(chainId: number): NativeCurrency | Token {
return (cachedNativeCurrency[chainId] = nativeCurrency) return (cachedNativeCurrency[chainId] = nativeCurrency)
} }
export function getSwapCurrencyId(currency: Currency): string {
if (currency.isToken) {
return currency.address
}
return NATIVE_CHAIN_ID
}
export const TOKEN_SHORTHANDS: { [shorthand: string]: { [chainId in SupportedChainId]?: string } } = { export const TOKEN_SHORTHANDS: { [shorthand: string]: { [chainId in SupportedChainId]?: string } } = {
USDC: { USDC: {
[SupportedChainId.MAINNET]: USDC_MAINNET.address, [SupportedChainId.MAINNET]: USDC_MAINNET.address,
......
...@@ -8,4 +8,5 @@ export enum FeatureFlag { ...@@ -8,4 +8,5 @@ export enum FeatureFlag {
swapWidget = 'swap_widget_replacement_enabled', swapWidget = 'swap_widget_replacement_enabled',
statsigDummy = 'web_dummy_gate_amplitude_id', statsigDummy = 'web_dummy_gate_amplitude_id',
detailsV2 = 'details_v2', detailsV2 = 'details_v2',
removeWidget = 'remove_widget_tdp',
} }
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useWidgetRemovalFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.removeWidget, BaseVariant.Control)
}
export function useWidgetRemovalEnabled(): boolean {
return useWidgetRemovalFlag() === BaseVariant.Enabled
}
export { BaseVariant as WidgetRemovalVariant }
...@@ -13,9 +13,10 @@ import { WrappedTokenInfo } from '../state/lists/wrappedTokenInfo' ...@@ -13,9 +13,10 @@ import { WrappedTokenInfo } from '../state/lists/wrappedTokenInfo'
import { useUserAddedTokens, useUserAddedTokensOnChain } from '../state/user/hooks' import { useUserAddedTokens, useUserAddedTokensOnChain } from '../state/user/hooks'
import { TokenAddressMap, useUnsupportedTokenList } from './../state/lists/hooks' import { TokenAddressMap, useUnsupportedTokenList } from './../state/lists/hooks'
type Maybe<T> = T | null | undefined
// reduce token map into standard address <-> Token mapping, optionally include user added tokens // reduce token map into standard address <-> Token mapping, optionally include user added tokens
function useTokensFromMap(tokenMap: TokenAddressMap): { [address: string]: Token } { function useTokensFromMap(tokenMap: TokenAddressMap, chainId: Maybe<SupportedChainId>): { [address: string]: Token } {
const { chainId } = useWeb3React()
return useMemo(() => { return useMemo(() => {
if (!chainId) return {} if (!chainId) return {}
...@@ -32,9 +33,9 @@ export function useAllTokensMultichain(): TokenAddressMap { ...@@ -32,9 +33,9 @@ export function useAllTokensMultichain(): TokenAddressMap {
} }
// Returns all tokens from the default list + user added tokens // Returns all tokens from the default list + user added tokens
export function useDefaultActiveTokens(): { [address: string]: Token } { export function useDefaultActiveTokens(chainId: Maybe<SupportedChainId>): { [address: string]: Token } {
const defaultListTokens = useCombinedActiveList() const defaultListTokens = useCombinedActiveList()
const tokensFromMap = useTokensFromMap(defaultListTokens) const tokensFromMap = useTokensFromMap(defaultListTokens, chainId)
const userAddedTokens = useUserAddedTokens() const userAddedTokens = useUserAddedTokens()
return useMemo(() => { return useMemo(() => {
return ( return (
...@@ -66,7 +67,7 @@ export function useUnsupportedTokens(): { [address: string]: Token } { ...@@ -66,7 +67,7 @@ export function useUnsupportedTokens(): { [address: string]: Token } {
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const listsByUrl = useAllLists() const listsByUrl = useAllLists()
const unsupportedTokensMap = useUnsupportedTokenList() const unsupportedTokensMap = useUnsupportedTokenList()
const unsupportedTokens = useTokensFromMap(unsupportedTokensMap) const unsupportedTokens = useTokensFromMap(unsupportedTokensMap, chainId)
// checks the default L2 lists to see if `bridgeInfo` has an L1 address value that is unsupported // checks the default L2 lists to see if `bridgeInfo` has an L1 address value that is unsupported
const l2InferredBlockedTokens: typeof unsupportedTokens = useMemo(() => { const l2InferredBlockedTokens: typeof unsupportedTokens = useMemo(() => {
...@@ -110,7 +111,7 @@ export function useSearchInactiveTokenLists(search: string | undefined, minResul ...@@ -110,7 +111,7 @@ export function useSearchInactiveTokenLists(search: string | undefined, minResul
const lists = useAllLists() const lists = useAllLists()
const inactiveUrls = DEFAULT_INACTIVE_LIST_URLS const inactiveUrls = DEFAULT_INACTIVE_LIST_URLS
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const activeTokens = useDefaultActiveTokens() const activeTokens = useDefaultActiveTokens(chainId)
return useMemo(() => { return useMemo(() => {
if (!search || search.trim().length === 0) return [] if (!search || search.trim().length === 0) return []
const tokenFilter = getTokenFilter(search) const tokenFilter = getTokenFilter(search)
...@@ -167,11 +168,13 @@ export function useIsUserAddedTokenOnChain( ...@@ -167,11 +168,13 @@ export function useIsUserAddedTokenOnChain(
// null if loading or null was passed // null if loading or null was passed
// otherwise returns the token // otherwise returns the token
export function useToken(tokenAddress?: string | null): Token | null | undefined { export function useToken(tokenAddress?: string | null): Token | null | undefined {
const tokens = useDefaultActiveTokens() const { chainId } = useWeb3React()
const tokens = useDefaultActiveTokens(chainId)
return useTokenFromMapOrNetwork(tokens, tokenAddress) return useTokenFromMapOrNetwork(tokens, tokenAddress)
} }
export function useCurrency(currencyId?: string | null): Currency | null | undefined { export function useCurrency(currencyId: Maybe<string>, chainId?: SupportedChainId): Currency | null | undefined {
const tokens = useDefaultActiveTokens() const { chainId: connectedChainId } = useWeb3React()
return useCurrencyFromMap(tokens, currencyId) const tokens = useDefaultActiveTokens(chainId ?? connectedChainId)
return useCurrencyFromMap(tokens, chainId ?? connectedChainId, currencyId)
} }
...@@ -81,7 +81,7 @@ export default function useAutoSlippageTolerance( ...@@ -81,7 +81,7 @@ export default function useAutoSlippageTolerance(
const nativeGasPrice = useGasPrice() const nativeGasPrice = useGasPrice()
const gasEstimate = guesstimateGas(trade) const gasEstimate = guesstimateGas(trade)
const nativeCurrency = useNativeCurrency() const nativeCurrency = useNativeCurrency(chainId)
const nativeCurrencyPrice = useStablecoinPrice((trade && nativeCurrency) ?? undefined) const nativeCurrencyPrice = useStablecoinPrice((trade && nativeCurrency) ?? undefined)
return useMemo(() => { return useMemo(() => {
......
import { Token } from '@uniswap/sdk-core' import { Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { useMemo } from 'react' import { useMemo } from 'react'
import { PositionDetails } from 'types/position' import { PositionDetails } from 'types/position'
import { hasURL } from 'utils/urlChecks' import { hasURL } from 'utils/urlChecks'
...@@ -23,7 +24,8 @@ function getUniqueAddressesFromPositions(positions: PositionDetails[]): string[] ...@@ -23,7 +24,8 @@ function getUniqueAddressesFromPositions(positions: PositionDetails[]): string[]
* The hope is that this approach removes the cheapest version of the attack without punishing non-malicious url symbols * The hope is that this approach removes the cheapest version of the attack without punishing non-malicious url symbols
*/ */
export function useFilterPossiblyMaliciousPositions(positions: PositionDetails[]): PositionDetails[] { export function useFilterPossiblyMaliciousPositions(positions: PositionDetails[]): PositionDetails[] {
const activeTokensList = useDefaultActiveTokens() const { chainId } = useWeb3React()
const activeTokensList = useDefaultActiveTokens(chainId)
const nonListPositionTokenAddresses = useMemo( const nonListPositionTokenAddresses = useMemo(
() => getUniqueAddressesFromPositions(positions).filter((address) => !activeTokensList[address]), () => getUniqueAddressesFromPositions(positions).filter((address) => !activeTokensList[address]),
......
...@@ -31,7 +31,8 @@ enum WrapInputError { ...@@ -31,7 +31,8 @@ enum WrapInputError {
} }
export function WrapErrorText({ wrapInputError }: { wrapInputError: WrapInputError }) { export function WrapErrorText({ wrapInputError }: { wrapInputError: WrapInputError }) {
const native = useNativeCurrency() const { chainId } = useWeb3React()
const native = useNativeCurrency(chainId)
const wrapped = native?.wrapped const wrapped = native?.wrapped
switch (wrapInputError) { switch (wrapInputError) {
......
...@@ -2,7 +2,7 @@ import { arrayify } from '@ethersproject/bytes' ...@@ -2,7 +2,7 @@ import { arrayify } from '@ethersproject/bytes'
import { parseBytes32String } from '@ethersproject/strings' import { parseBytes32String } from '@ethersproject/strings'
import { Currency, Token } from '@uniswap/sdk-core' import { Currency, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { isSupportedChain } from 'constants/chains' import { isSupportedChain, SupportedChainId } from 'constants/chains'
import { useBytes32TokenContract, useTokenContract } from 'hooks/useContract' import { useBytes32TokenContract, useTokenContract } from 'hooks/useContract'
import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall' import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall'
import useNativeCurrency from 'lib/hooks/useNativeCurrency' import useNativeCurrency from 'lib/hooks/useNativeCurrency'
...@@ -92,9 +92,12 @@ export function useTokenFromMapOrNetwork(tokens: TokenMap, tokenAddress?: string ...@@ -92,9 +92,12 @@ export function useTokenFromMapOrNetwork(tokens: TokenMap, tokenAddress?: string
* Returns null if currency is loading or null was passed. * Returns null if currency is loading or null was passed.
* Returns undefined if currencyId is invalid or token does not exist. * Returns undefined if currencyId is invalid or token does not exist.
*/ */
export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null): Currency | null | undefined { export function useCurrencyFromMap(
const nativeCurrency = useNativeCurrency() tokens: TokenMap,
const { chainId } = useWeb3React() chainId: SupportedChainId | undefined,
currencyId?: string | null
): Currency | null | undefined {
const nativeCurrency = useNativeCurrency(chainId)
const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH') const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH')
const shorthandMatchAddress = useMemo(() => { const shorthandMatchAddress = useMemo(() => {
const chain = supportedChainId(chainId) const chain = supportedChainId(chainId)
...@@ -108,6 +111,5 @@ export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null) ...@@ -108,6 +111,5 @@ export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null)
// this case so we use our builtin wrapped token instead of wrapped tokens on token lists // this case so we use our builtin wrapped token instead of wrapped tokens on token lists
const wrappedNative = nativeCurrency?.wrapped const wrappedNative = nativeCurrency?.wrapped
if (wrappedNative?.address?.toUpperCase() === currencyId?.toUpperCase()) return wrappedNative if (wrappedNative?.address?.toUpperCase() === currencyId?.toUpperCase()) return wrappedNative
return isNative ? nativeCurrency : token return isNative ? nativeCurrency : token
} }
import { NativeCurrency, Token } from '@uniswap/sdk-core' import { NativeCurrency, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import { nativeOnChain } from 'constants/tokens' import { nativeOnChain } from 'constants/tokens'
import { useMemo } from 'react' import { useMemo } from 'react'
export default function useNativeCurrency(): NativeCurrency | Token { export default function useNativeCurrency(chainId: SupportedChainId | null | undefined): NativeCurrency | Token {
const { chainId } = useWeb3React()
return useMemo( return useMemo(
() => () =>
chainId chainId
......
...@@ -183,7 +183,7 @@ const EthValueWrapper = styled.span<{ totalEthListingValue: boolean }>` ...@@ -183,7 +183,7 @@ const EthValueWrapper = styled.span<{ totalEthListingValue: boolean }>`
export const ListPage = () => { export const ListPage = () => {
const { setProfilePageState: setSellPageState } = useProfilePageState() const { setProfilePageState: setSellPageState } = useProfilePageState()
const { provider } = useWeb3React() const { provider, chainId } = useWeb3React()
const isMobile = useIsMobile() const isMobile = useIsMobile()
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING }) const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
const { setGlobalMarketplaces, sellAssets, issues } = useSellAsset( const { setGlobalMarketplaces, sellAssets, issues } = useSellAsset(
...@@ -205,7 +205,7 @@ export const ListPage = () => { ...@@ -205,7 +205,7 @@ export const ListPage = () => {
) )
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets]) const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
const nativeCurrency = useNativeCurrency() const nativeCurrency = useNativeCurrency(chainId)
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency) const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
const usdcValue = useStablecoinValue(parsedAmount) const usdcValue = useStablecoinValue(parsedAmount)
const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice) const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice)
......
...@@ -45,7 +45,7 @@ const ListModalWrapper = styled.div` ...@@ -45,7 +45,7 @@ const ListModalWrapper = styled.div`
` `
export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => { export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
const { provider } = useWeb3React() const { provider, chainId } = useWeb3React()
const signer = provider?.getSigner() const signer = provider?.getSigner()
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING }) const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
const sellAssets = useSellAsset((state) => state.sellAssets) const sellAssets = useSellAsset((state) => state.sellAssets)
...@@ -72,7 +72,7 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => { ...@@ -72,7 +72,7 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
(s) => (s === Section.APPROVE ? Section.SIGN : Section.APPROVE), (s) => (s === Section.APPROVE ? Section.SIGN : Section.APPROVE),
Section.APPROVE Section.APPROVE
) )
const nativeCurrency = useNativeCurrency() const nativeCurrency = useNativeCurrency(chainId)
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency) const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
const usdcValue = useStablecoinValue(parsedAmount) const usdcValue = useStablecoinValue(parsedAmount)
const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice) const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice)
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column' import Column from 'components/Column'
import { ScrollBarStyles } from 'components/Common' import { ScrollBarStyles } from 'components/Common'
import Row from 'components/Row' import Row from 'components/Row'
...@@ -76,7 +77,8 @@ const TweetRow = styled(Row)` ...@@ -76,7 +77,8 @@ const TweetRow = styled(Row)`
export const SuccessScreen = ({ overlayClick }: { overlayClick: () => void }) => { export const SuccessScreen = ({ overlayClick }: { overlayClick: () => void }) => {
const theme = useTheme() const theme = useTheme()
const sellAssets = useSellAsset((state) => state.sellAssets) const sellAssets = useSellAsset((state) => state.sellAssets)
const nativeCurrency = useNativeCurrency() const { chainId } = useWeb3React()
const nativeCurrency = useNativeCurrency(chainId)
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets]) const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency) const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
......
...@@ -403,7 +403,7 @@ function PositionPageContent() { ...@@ -403,7 +403,7 @@ function PositionPageContent() {
// flag for receiving WETH // flag for receiving WETH
const [receiveWETH, setReceiveWETH] = useState(false) const [receiveWETH, setReceiveWETH] = useState(false)
const nativeCurrency = useNativeCurrency() const nativeCurrency = useNativeCurrency(chainId)
const nativeWrappedSymbol = nativeCurrency.wrapped.symbol const nativeWrappedSymbol = nativeCurrency.wrapped.symbol
// construct Position from details returned // construct Position from details returned
......
...@@ -74,7 +74,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) { ...@@ -74,7 +74,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
// flag for receiving WETH // flag for receiving WETH
const [receiveWETH, setReceiveWETH] = useState(false) const [receiveWETH, setReceiveWETH] = useState(false)
const nativeCurrency = useNativeCurrency() const nativeCurrency = useNativeCurrency(chainId)
const nativeWrappedSymbol = nativeCurrency.wrapped.symbol const nativeWrappedSymbol = nativeCurrency.wrapped.symbol
// burn state // burn state
......
...@@ -18,14 +18,13 @@ import Loader from 'components/Icons/LoadingSpinner' ...@@ -18,14 +18,13 @@ import Loader from 'components/Icons/LoadingSpinner'
import { NetworkAlert } from 'components/NetworkAlert/NetworkAlert' import { NetworkAlert } from 'components/NetworkAlert/NetworkAlert'
import PriceImpactWarning from 'components/swap/PriceImpactWarning' import PriceImpactWarning from 'components/swap/PriceImpactWarning'
import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown' import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal' import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import Widget from 'components/Widget' import { getChainInfo } from 'constants/chainInfo'
import { isSupportedChain } from 'constants/chains' import { isSupportedChain, SupportedChainId } from 'constants/chains'
import { useSwapWidgetEnabled } from 'featureFlags/flags/swapWidget'
import useENSAddress from 'hooks/useENSAddress' import useENSAddress from 'hooks/useENSAddress'
import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance' import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance'
import usePrevious from 'hooks/usePrevious'
import { useSwapCallback } from 'hooks/useSwapCallback' import { useSwapCallback } from 'hooks/useSwapCallback'
import { useUSDPrice } from 'hooks/useUSDPrice' import { useUSDPrice } from 'hooks/useUSDPrice'
import JSBI from 'jsbi' import JSBI from 'jsbi'
...@@ -40,6 +39,7 @@ import { TradeState } from 'state/routing/types' ...@@ -40,6 +39,7 @@ import { TradeState } from 'state/routing/types'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
import invariant from 'tiny-invariant' import invariant from 'tiny-invariant'
import { currencyAmountToPreciseFloat, formatTransactionAmount } from 'utils/formatNumbers' import { currencyAmountToPreciseFloat, formatTransactionAmount } from 'utils/formatNumbers'
import { switchChain } from 'utils/switchChain'
import AddressInputPanel from '../../components/AddressInputPanel' import AddressInputPanel from '../../components/AddressInputPanel'
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
...@@ -52,13 +52,13 @@ import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal' ...@@ -52,13 +52,13 @@ import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal'
import { ArrowWrapper, PageWrapper, SwapCallbackError, SwapWrapper } from '../../components/swap/styleds' import { ArrowWrapper, PageWrapper, SwapCallbackError, SwapWrapper } from '../../components/swap/styleds'
import SwapHeader from '../../components/swap/SwapHeader' import SwapHeader from '../../components/swap/SwapHeader'
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink' import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import { TOKEN_SHORTHANDS } from '../../constants/tokens' import { getSwapCurrencyId, TOKEN_SHORTHANDS } from '../../constants/tokens'
import { useCurrency, useDefaultActiveTokens } from '../../hooks/Tokens' import { useCurrency, useDefaultActiveTokens } from '../../hooks/Tokens'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported' import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import useWrapCallback, { WrapErrorText, WrapType } from '../../hooks/useWrapCallback' import useWrapCallback, { WrapErrorText, WrapType } from '../../hooks/useWrapCallback'
import { Field } from '../../state/swap/actions' import { Field, replaceSwapState } from '../../state/swap/actions'
import { useDefaultsFromURLSearch, useDerivedSwapInfo, useSwapActionHandlers } from '../../state/swap/hooks' import { useDefaultsFromURLSearch, useDerivedSwapInfo, useSwapActionHandlers } from '../../state/swap/hooks'
import swapReducer, { initialState as initialSwapState } from '../../state/swap/reducer' import swapReducer, { initialState as initialSwapState, SwapState } from '../../state/swap/reducer'
import { useExpertModeManager } from '../../state/user/hooks' import { useExpertModeManager } from '../../state/user/hooks'
import { LinkStyledButton, ThemedText } from '../../theme' import { LinkStyledButton, ThemedText } from '../../theme'
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact' import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
...@@ -143,19 +143,65 @@ function largerPercentValue(a?: Percent, b?: Percent) { ...@@ -143,19 +143,65 @@ function largerPercentValue(a?: Percent, b?: Percent) {
const TRADE_STRING = 'SwapRouter' const TRADE_STRING = 'SwapRouter'
export default function Swap({ className }: { className?: string }) { export default function SwapPage({ className }: { className?: string }) {
const navigate = useNavigate() const { chainId: connectedChainId } = useWeb3React()
const { account, chainId } = useWeb3React()
const loadedUrlParams = useDefaultsFromURLSearch() const loadedUrlParams = useDefaultsFromURLSearch()
return (
<Trace page={InterfacePageName.SWAP_PAGE} shouldLogImpression>
<>
<PageWrapper>
<Swap
className={className}
chainId={connectedChainId}
prefilledState={{
[Field.INPUT]: { currencyId: loadedUrlParams?.[Field.INPUT]?.currencyId },
[Field.OUTPUT]: { currencyId: loadedUrlParams?.[Field.OUTPUT]?.currencyId },
}}
/>
<NetworkAlert />
</PageWrapper>
<SwitchLocaleLink />
</>
</Trace>
)
}
/**
* The swap component displays the swap interface, manages state for the swap, and triggers onchain swaps.
*
* In most cases, chainId should refer to the connected chain, i.e. `useWeb3React().chainId`.
* However if this component is being used in a context that displays information from a different, unconnected
* chain (e.g. the TDP), then chainId should refer to the unconnected chain.
*/
export function Swap({
className,
prefilledState = {},
chainId,
onCurrencyChange,
disableTokenInputs = false,
}: {
className?: string
prefilledState?: Partial<SwapState>
chainId: SupportedChainId | undefined
onCurrencyChange?: (selected: Pick<SwapState, Field.INPUT | Field.OUTPUT>) => void
disableTokenInputs?: boolean
}) {
const { account, chainId: connectedChainId, connector } = useWeb3React()
const [newSwapQuoteNeedsLogging, setNewSwapQuoteNeedsLogging] = useState(true) const [newSwapQuoteNeedsLogging, setNewSwapQuoteNeedsLogging] = useState(true)
const [fetchingSwapQuoteStartTime, setFetchingSwapQuoteStartTime] = useState<Date | undefined>() const [fetchingSwapQuoteStartTime, setFetchingSwapQuoteStartTime] = useState<Date | undefined>()
const swapWidgetEnabled = useSwapWidgetEnabled()
// token warning stuff // token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [ const prefilledInputCurrency = useCurrency(prefilledState?.[Field.INPUT]?.currencyId)
useCurrency(loadedUrlParams?.[Field.INPUT]?.currencyId), const prefilledOutputCurrency = useCurrency(prefilledState?.[Field.OUTPUT]?.currencyId)
useCurrency(loadedUrlParams?.[Field.OUTPUT]?.currencyId),
] const [loadedInputCurrency, setLoadedInputCurrency] = useState(prefilledInputCurrency)
const [loadedOutputCurrency, setLoadedOutputCurrency] = useState(prefilledOutputCurrency)
useEffect(() => {
setLoadedInputCurrency(prefilledInputCurrency)
setLoadedOutputCurrency(prefilledOutputCurrency)
}, [prefilledInputCurrency, prefilledOutputCurrency])
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false) const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const urlLoadedTokens: Token[] = useMemo( const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c?.isToken ?? false) ?? [], () => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c?.isToken ?? false) ?? [],
...@@ -166,7 +212,7 @@ export default function Swap({ className }: { className?: string }) { ...@@ -166,7 +212,7 @@ export default function Swap({ className }: { className?: string }) {
}, []) }, [])
// dismiss warning if all imported tokens are in active lists // dismiss warning if all imported tokens are in active lists
const defaultTokens = useDefaultActiveTokens() const defaultTokens = useDefaultActiveTokens(chainId)
const importTokensNotInDefault = useMemo( const importTokensNotInDefault = useMemo(
() => () =>
urlLoadedTokens && urlLoadedTokens &&
...@@ -194,8 +240,33 @@ export default function Swap({ className }: { className?: string }) { ...@@ -194,8 +240,33 @@ export default function Swap({ className }: { className?: string }) {
// for expert mode // for expert mode
const [isExpertMode] = useExpertModeManager() const [isExpertMode] = useExpertModeManager()
// swap state // swap state
const [state, dispatch] = useReducer(swapReducer, initialSwapState) const [state, dispatch] = useReducer(swapReducer, { ...initialSwapState, ...prefilledState })
const { typedValue, recipient, independentField } = state const { typedValue, recipient, independentField } = state
const previousConnectedChainId = usePrevious(connectedChainId)
const previousPrefilledState = usePrevious(prefilledState)
useEffect(() => {
const combinedInitialState = { ...initialSwapState, ...prefilledState }
const chainChanged = previousConnectedChainId && previousConnectedChainId !== connectedChainId
const prefilledInputChanged =
previousPrefilledState &&
previousPrefilledState?.[Field.INPUT]?.currencyId !== prefilledState?.[Field.INPUT]?.currencyId
const prefilledOutputChanged =
previousPrefilledState &&
previousPrefilledState?.[Field.OUTPUT]?.currencyId !== prefilledState?.[Field.OUTPUT]?.currencyId
if (chainChanged || prefilledInputChanged || prefilledOutputChanged) {
dispatch(
replaceSwapState({
...initialSwapState,
...prefilledState,
field: combinedInitialState.independentField ?? Field.INPUT,
inputCurrencyId: combinedInitialState.INPUT.currencyId ?? undefined,
outputCurrencyId: combinedInitialState.OUTPUT.currencyId ?? undefined,
})
)
}
}, [connectedChainId, prefilledState, previousConnectedChainId, previousPrefilledState])
const { const {
trade: { state: tradeState, trade }, trade: { state: tradeState, trade },
allowedSlippage, allowedSlippage,
...@@ -203,7 +274,7 @@ export default function Swap({ className }: { className?: string }) { ...@@ -203,7 +274,7 @@ export default function Swap({ className }: { className?: string }) {
parsedAmount, parsedAmount,
currencies, currencies,
inputError: swapInputError, inputError: swapInputError,
} = useDerivedSwapInfo(state) } = useDerivedSwapInfo(state, chainId)
const { const {
wrapType, wrapType,
...@@ -261,6 +332,9 @@ export default function Swap({ className }: { className?: string }) { ...@@ -261,6 +332,9 @@ export default function Swap({ className }: { className?: string }) {
[onUserInput] [onUserInput]
) )
const navigate = useNavigate()
const swapIsUnsupported = useIsSwapUnsupported(currencies[Field.INPUT], currencies[Field.OUTPUT])
// reset if they close warning without tokens in params // reset if they close warning without tokens in params
const handleDismissTokenWarning = useCallback(() => { const handleDismissTokenWarning = useCallback(() => {
setDismissTokenWarning(true) setDismissTokenWarning(true)
...@@ -418,8 +492,14 @@ export default function Swap({ className }: { className?: string }) { ...@@ -418,8 +492,14 @@ export default function Swap({ className }: { className?: string }) {
const handleInputSelect = useCallback( const handleInputSelect = useCallback(
(inputCurrency: Currency) => { (inputCurrency: Currency) => {
onCurrencySelection(Field.INPUT, inputCurrency) onCurrencySelection(Field.INPUT, inputCurrency)
onCurrencyChange?.({
[Field.INPUT]: {
currencyId: getSwapCurrencyId(inputCurrency),
}, },
[onCurrencySelection] [Field.OUTPUT]: state[Field.OUTPUT],
})
},
[onCurrencyChange, onCurrencySelection, state]
) )
const handleMaxInput = useCallback(() => { const handleMaxInput = useCallback(() => {
...@@ -431,12 +511,18 @@ export default function Swap({ className }: { className?: string }) { ...@@ -431,12 +511,18 @@ export default function Swap({ className }: { className?: string }) {
}, [maxInputAmount, onUserInput]) }, [maxInputAmount, onUserInput])
const handleOutputSelect = useCallback( const handleOutputSelect = useCallback(
(outputCurrency: Currency) => onCurrencySelection(Field.OUTPUT, outputCurrency), (outputCurrency: Currency) => {
[onCurrencySelection] onCurrencySelection(Field.OUTPUT, outputCurrency)
onCurrencyChange?.({
[Field.INPUT]: state[Field.INPUT],
[Field.OUTPUT]: {
currencyId: getSwapCurrencyId(outputCurrency),
},
})
},
[onCurrencyChange, onCurrencySelection, state]
) )
const swapIsUnsupported = useIsSwapUnsupported(currencies[Field.INPUT], currencies[Field.OUTPUT])
const priceImpactTooHigh = priceImpactSeverity > 3 && !isExpertMode const priceImpactTooHigh = priceImpactSeverity > 3 && !isExpertMode
const showPriceImpactWarning = largerPriceImpact && priceImpactSeverity > 3 const showPriceImpactWarning = largerPriceImpact && priceImpactSeverity > 3
...@@ -477,8 +563,7 @@ export default function Swap({ className }: { className?: string }) { ...@@ -477,8 +563,7 @@ export default function Swap({ className }: { className?: string }) {
) )
return ( return (
<Trace page={InterfacePageName.SWAP_PAGE} shouldLogImpression> <SwapWrapper chainId={chainId} className={className} id="swap-page">
<>
<TokenSafetyModal <TokenSafetyModal
isOpen={importTokensNotInDefault.length > 0 && !dismissTokenWarning} isOpen={importTokensNotInDefault.length > 0 && !dismissTokenWarning}
tokenAddress={importTokensNotInDefault[0]?.address} tokenAddress={importTokensNotInDefault[0]?.address}
...@@ -487,17 +572,6 @@ export default function Swap({ className }: { className?: string }) { ...@@ -487,17 +572,6 @@ export default function Swap({ className }: { className?: string }) {
onCancel={handleDismissTokenWarning} onCancel={handleDismissTokenWarning}
showCancel={true} showCancel={true}
/> />
<PageWrapper>
{swapWidgetEnabled ? (
<Widget
defaultTokens={{
[Field.INPUT]: loadedInputCurrency ?? undefined,
[Field.OUTPUT]: loadedOutputCurrency ?? undefined,
}}
width="100%"
/>
) : (
<SwapWrapper chainId={chainId} className={className} id="swap-page">
<SwapHeader allowedSlippage={allowedSlippage} /> <SwapHeader allowedSlippage={allowedSlippage} />
<ConfirmSwapModal <ConfirmSwapModal
isOpen={showConfirm} isOpen={showConfirm}
...@@ -521,12 +595,9 @@ export default function Swap({ className }: { className?: string }) { ...@@ -521,12 +595,9 @@ export default function Swap({ className }: { className?: string }) {
<Trace section={InterfaceSectionName.CURRENCY_INPUT_PANEL}> <Trace section={InterfaceSectionName.CURRENCY_INPUT_PANEL}>
<SwapCurrencyInputPanel <SwapCurrencyInputPanel
label={ label={
independentField === Field.OUTPUT && !showWrap ? ( independentField === Field.OUTPUT && !showWrap ? <Trans>From (at most)</Trans> : <Trans>From</Trans>
<Trans>From (at most)</Trans>
) : (
<Trans>From</Trans>
)
} }
disabled={disableTokenInputs}
value={formattedAmounts[Field.INPUT]} value={formattedAmounts[Field.INPUT]}
showMaxButton={showMaxButton} showMaxButton={showMaxButton}
currency={currencies[Field.INPUT] ?? null} currency={currencies[Field.INPUT] ?? null}
...@@ -550,15 +621,13 @@ export default function Swap({ className }: { className?: string }) { ...@@ -550,15 +621,13 @@ export default function Swap({ className }: { className?: string }) {
<ArrowContainer <ArrowContainer
data-testid="swap-currency-button" data-testid="swap-currency-button"
onClick={() => { onClick={() => {
onSwitchTokens() !disableTokenInputs && onSwitchTokens()
}} }}
color={theme.textPrimary} color={theme.textPrimary}
> >
<ArrowDown <ArrowDown
size="16" size="16"
color={ color={currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.textPrimary : theme.textTertiary}
currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.textPrimary : theme.textTertiary
}
/> />
</ArrowContainer> </ArrowContainer>
</TraceEvent> </TraceEvent>
...@@ -570,14 +639,9 @@ export default function Swap({ className }: { className?: string }) { ...@@ -570,14 +639,9 @@ export default function Swap({ className }: { className?: string }) {
<Trace section={InterfaceSectionName.CURRENCY_OUTPUT_PANEL}> <Trace section={InterfaceSectionName.CURRENCY_OUTPUT_PANEL}>
<SwapCurrencyInputPanel <SwapCurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]} value={formattedAmounts[Field.OUTPUT]}
disabled={disableTokenInputs}
onUserInput={handleTypeOutput} onUserInput={handleTypeOutput}
label={ label={independentField === Field.INPUT && !showWrap ? <Trans>To (at least)</Trans> : <Trans>To</Trans>}
independentField === Field.INPUT && !showWrap ? (
<Trans>To (at least)</Trans>
) : (
<Trans>To</Trans>
)
}
showMaxButton={false} showMaxButton={false}
hideBalance={false} hideBalance={false}
fiatValue={fiatValueOutput} fiatValue={fiatValueOutput}
...@@ -590,7 +654,6 @@ export default function Swap({ className }: { className?: string }) { ...@@ -590,7 +654,6 @@ export default function Swap({ className }: { className?: string }) {
loading={independentField === Field.INPUT && routeIsSyncing} loading={independentField === Field.INPUT && routeIsSyncing}
/> />
</Trace> </Trace>
{recipient !== null && !showWrap ? ( {recipient !== null && !showWrap ? (
<> <>
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}> <AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
...@@ -635,6 +698,14 @@ export default function Swap({ className }: { className?: string }) { ...@@ -635,6 +698,14 @@ export default function Swap({ className }: { className?: string }) {
<Trans>Connect Wallet</Trans> <Trans>Connect Wallet</Trans>
</ButtonLight> </ButtonLight>
</TraceEvent> </TraceEvent>
) : chainId && chainId !== connectedChainId ? (
<ButtonPrimary
onClick={() => {
switchChain(connector, chainId)
}}
>
Connect to {getChainInfo(chainId)?.label}
</ButtonPrimary>
) : showWrap ? ( ) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap} fontWeight={600}> <ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap} fontWeight={600}>
{wrapInputError ? ( {wrapInputError ? (
...@@ -673,8 +744,8 @@ export default function Swap({ className }: { className?: string }) { ...@@ -673,8 +744,8 @@ export default function Swap({ className }: { className?: string }) {
<MouseoverTooltip <MouseoverTooltip
text={ text={
<Trans> <Trans>
Permission is required for Uniswap to swap each token. This will expire after one Permission is required for Uniswap to swap each token. This will expire after one month for
month for your security. your security.
</Trans> </Trans>
} }
> >
...@@ -729,17 +800,5 @@ export default function Swap({ className }: { className?: string }) { ...@@ -729,17 +800,5 @@ export default function Swap({ className }: { className?: string }) {
</div> </div>
</AutoColumn> </AutoColumn>
</SwapWrapper> </SwapWrapper>
)}
<NetworkAlert />
</PageWrapper>
<SwitchLocaleLink />
{!swapIsUnsupported ? null : (
<UnsupportedCurrencyFooter
show={swapIsUnsupported}
currencies={[currencies[Field.INPUT], currencies[Field.OUTPUT]]}
/>
)}
</>
</Trace>
) )
} }
...@@ -16,8 +16,8 @@ export { ...@@ -16,8 +16,8 @@ export {
// mimics useAllBalances // mimics useAllBalances
export function useAllTokenBalances(): [{ [tokenAddress: string]: CurrencyAmount<Token> | undefined }, boolean] { export function useAllTokenBalances(): [{ [tokenAddress: string]: CurrencyAmount<Token> | undefined }, boolean] {
const { account } = useWeb3React() const { account, chainId } = useWeb3React()
const allTokens = useDefaultActiveTokens() const allTokens = useDefaultActiveTokens(chainId)
const allTokensArray = useMemo(() => Object.values(allTokens ?? {}), [allTokens]) const allTokensArray = useMemo(() => Object.values(allTokens ?? {}), [allTokens])
const [balances, balancesIsLoading] = useTokenBalancesWithLoadingIndicator(account ?? undefined, allTokensArray) const [balances, balancesIsLoading] = useTokenBalancesWithLoadingIndicator(account ?? undefined, allTokensArray)
return [balances ?? {}, balancesIsLoading] return [balances ?? {}, balancesIsLoading]
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { SupportedChainId } from 'constants/chains'
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance' import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
import { useBestTrade } from 'hooks/useBestTrade' import { useBestTrade } from 'hooks/useBestTrade'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
...@@ -71,7 +72,10 @@ const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = { ...@@ -71,7 +72,10 @@ const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = {
} }
// from the current swap inputs, compute the best trade and return it. // from the current swap inputs, compute the best trade and return it.
export function useDerivedSwapInfo(state: SwapState): { export function useDerivedSwapInfo(
state: SwapState,
chainId: SupportedChainId | undefined
): {
currencies: { [field in Field]?: Currency | null } currencies: { [field in Field]?: Currency | null }
currencyBalances: { [field in Field]?: CurrencyAmount<Currency> } currencyBalances: { [field in Field]?: CurrencyAmount<Currency> }
parsedAmount: CurrencyAmount<Currency> | undefined parsedAmount: CurrencyAmount<Currency> | undefined
...@@ -92,8 +96,8 @@ export function useDerivedSwapInfo(state: SwapState): { ...@@ -92,8 +96,8 @@ export function useDerivedSwapInfo(state: SwapState): {
recipient, recipient,
} = state } = state
const inputCurrency = useCurrency(inputCurrencyId) const inputCurrency = useCurrency(inputCurrencyId, chainId)
const outputCurrency = useCurrency(outputCurrencyId) const outputCurrency = useCurrency(outputCurrencyId, chainId)
const recipientLookup = useENS(recipient ?? undefined) const recipientLookup = useENS(recipient ?? undefined)
const to: string | null = (recipient === null ? account : recipientLookup.address) ?? null const to: string | null = (recipient === null ? account : recipientLookup.address) ?? null
......
...@@ -271,7 +271,7 @@ export function toV2LiquidityToken([tokenA, tokenB]: [Token, Token]): Token { ...@@ -271,7 +271,7 @@ export function toV2LiquidityToken([tokenA, tokenB]: [Token, Token]): Token {
*/ */
export function useTrackedTokenPairs(): [Token, Token][] { export function useTrackedTokenPairs(): [Token, Token][] {
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const tokens = useDefaultActiveTokens() const tokens = useDefaultActiveTokens(chainId)
// pinned pairs // pinned pairs
const pinnedPairs = useMemo(() => (chainId ? PINNED_PAIRS[chainId] ?? [] : []), [chainId]) const pinnedPairs = useMemo(() => (chainId ? PINNED_PAIRS[chainId] ?? [] : []), [chainId])
......
export function addressesAreEquivalent(a?: string, b?: string) { export function addressesAreEquivalent(a: string | null | undefined, b: string | null | undefined) {
if (!a || !b) return false if (!a || !b) return false
return a === b || a.toLowerCase() === b.toLowerCase() return a === b || a.toLowerCase() === b.toLowerCase()
} }
...@@ -4,7 +4,7 @@ import { SupportedChainId } from 'constants/chains' ...@@ -4,7 +4,7 @@ import { SupportedChainId } from 'constants/chains'
* Returns the input chain ID if chain is supported. If not, return undefined * Returns the input chain ID if chain is supported. If not, return undefined
* @param chainId a chain ID, which will be returned if it is a supported chain ID * @param chainId a chain ID, which will be returned if it is a supported chain ID
*/ */
export function supportedChainId(chainId: number | undefined): SupportedChainId | undefined { export function supportedChainId(chainId: number | null | undefined): SupportedChainId | undefined {
if (typeof chainId === 'number' && chainId in SupportedChainId) { if (typeof chainId === 'number' && chainId in SupportedChainId) {
return chainId return chainId
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment