feat(swap): default 1 native to usdc on the swap page (#3347)

* feat(swap): default 1 eth to usdc on the swap page

* fix unit tests

* fix tests

* fix the issue better

* use the token list logo

* fix integration tests for swap and add one for eth/usdc

* address comments
This commit is contained in:
Moody Salem 2022-02-28 13:30:37 -05:00 committed by GitHub
parent c25d2b894c
commit fc34912b53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 194 additions and 126 deletions

@ -2,16 +2,27 @@ describe('Swap', () => {
beforeEach(() => {
cy.visit('/swap')
})
it('starts with an ETH/USDC swap and quotes it', () => {
cy.get('#swap-currency-input .token-amount-input').should('have.value', '1')
cy.get('#swap-currency-input .token-symbol-container').should('contain.text', 'ETH')
cy.get('#swap-currency-output .token-amount-input').should('not.have.value', '')
cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'USDC')
})
it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001')
cy.get('#swap-currency-input .token-amount-input')
.clear()
.type('0.001', { delay: 200 })
.should('have.value', '0.001')
})
it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0')
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0', { delay: 200 }).should('have.value', '0.0')
})
it('invalid swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').type('\\', { delay: 200 }).should('have.value', '')
cy.get('#swap-currency-input .token-amount-input').clear().type('\\', { delay: 200 }).should('have.value', '')
})
it('can enter an amount into output', () => {

@ -19,7 +19,7 @@ import { PositionDetails } from 'types/position'
import { formatTickPrice } from 'utils/formatTickPrice'
import { unwrappedToken } from 'utils/unwrappedToken'
import { DAI, USDC, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import { DAI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
const LinkRow = styled(Link)`
align-items: center;
@ -145,7 +145,7 @@ export function getPriceOrderingFromPositionForUI(position?: Position): {
const token1 = position.amount1.currency
// if token0 is a dollar-stable asset, set it as the quote token
const stables = [DAI, USDC, USDT]
const stables = [DAI, USDC_MAINNET, USDT]
if (stables.some((stable) => stable.equals(token0))) {
return {
priceLower: position.token0PriceUpper.invert(),

@ -1,7 +1,7 @@
import { Protocol } from '@uniswap/router-sdk'
import { Currency, Percent } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import { DAI, USDC, WBTC } from 'constants/tokens'
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
import { render } from 'test-utils'
import RoutingDiagram, { RoutingDiagramEntry } from './RoutingDiagram'
@ -10,16 +10,16 @@ const percent = (strings: TemplateStringsArray) => new Percent(parseInt(strings[
const singleRoute: RoutingDiagramEntry = {
percent: percent`100`,
path: [[USDC, DAI, FeeAmount.LOW]],
path: [[USDC_MAINNET, DAI, FeeAmount.LOW]],
protocol: Protocol.V3,
}
const multiRoute: RoutingDiagramEntry[] = [
{ percent: percent`75`, path: [[USDC, DAI, FeeAmount.LOWEST]], protocol: Protocol.V2 },
{ percent: percent`75`, path: [[USDC_MAINNET, DAI, FeeAmount.LOWEST]], protocol: Protocol.V2 },
{
percent: percent`25`,
path: [
[USDC, WBTC, FeeAmount.MEDIUM],
[USDC_MAINNET, WBTC, FeeAmount.MEDIUM],
[WBTC, DAI, FeeAmount.HIGH],
],
protocol: Protocol.V3,
@ -47,16 +47,16 @@ jest.mock('hooks/useTokenInfoFromActiveList', () => ({
}))
it('renders when no routes are provided', () => {
const { asFragment } = render(<RoutingDiagram currencyIn={DAI} currencyOut={USDC} routes={[]} />)
const { asFragment } = render(<RoutingDiagram currencyIn={DAI} currencyOut={USDC_MAINNET} routes={[]} />)
expect(asFragment()).toMatchSnapshot()
})
it('renders single route', () => {
const { asFragment } = render(<RoutingDiagram currencyIn={USDC} currencyOut={DAI} routes={[singleRoute]} />)
const { asFragment } = render(<RoutingDiagram currencyIn={USDC_MAINNET} currencyOut={DAI} routes={[singleRoute]} />)
expect(asFragment()).toMatchSnapshot()
})
it('renders multi route', () => {
const { asFragment } = render(<RoutingDiagram currencyIn={USDC} currencyOut={DAI} routes={multiRoute} />)
const { asFragment } = render(<RoutingDiagram currencyIn={USDC_MAINNET} currencyOut={DAI} routes={multiRoute} />)
expect(asFragment()).toMatchSnapshot()
})

@ -18,8 +18,8 @@ import {
sETH2,
SWISE,
TRIBE,
USDC,
USDC_ARBITRUM,
USDC_MAINNET,
USDC_OPTIMISM,
USDC_POLYGON,
USDT,
@ -50,7 +50,13 @@ const WRAPPED_NATIVE_CURRENCIES_ONLY: ChainTokenList = Object.fromEntries(
// used to construct intermediary pairs for trading
export const BASES_TO_CHECK_TRADES_AGAINST: ChainTokenList = {
...WRAPPED_NATIVE_CURRENCIES_ONLY,
[SupportedChainId.MAINNET]: [...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
[SupportedChainId.MAINNET]: [
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET],
DAI,
USDC_MAINNET,
USDT,
WBTC,
],
[SupportedChainId.OPTIMISM]: [
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.OPTIMISM],
DAI_OPTIMISM,
@ -101,7 +107,7 @@ export const COMMON_BASES: ChainCurrencyList = {
[SupportedChainId.MAINNET]: [
nativeOnChain(SupportedChainId.MAINNET),
DAI,
USDC,
USDC_MAINNET,
USDT,
WBTC,
WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET],
@ -154,7 +160,13 @@ export const COMMON_BASES: ChainCurrencyList = {
// used to construct the list of all pairs we consider by default in the frontend
export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = {
...WRAPPED_NATIVE_CURRENCIES_ONLY,
[SupportedChainId.MAINNET]: [...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
[SupportedChainId.MAINNET]: [
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET],
DAI,
USDC_MAINNET,
USDT,
WBTC,
],
}
export const PINNED_PAIRS: { readonly [chainId: number]: [Token, Token][] } = {
[SupportedChainId.MAINNET]: [
@ -168,7 +180,7 @@ export const PINNED_PAIRS: { readonly [chainId: number]: [Token, Token][] } = {
'Compound USD Coin'
),
],
[USDC, USDT],
[USDC_MAINNET, USDT],
[DAI, USDT],
],
}

@ -1,8 +1,23 @@
import { Currency, Ether, NativeCurrency, Token, WETH9 } from '@uniswap/sdk-core'
import {
USDC_ARBITRUM,
USDC_ARBITRUM_RINKEBY,
USDC_GÖRLI,
USDC_KOVAN,
USDC_MAINNET,
USDC_OPTIMISM,
USDC_OPTIMISTIC_KOVAN,
USDC_POLYGON,
USDC_POLYGON_MUMBAI,
USDC_RINKEBY,
USDC_ROPSTEN,
} from '@uniswap/smart-order-router'
import { UNI_ADDRESS } from './addresses'
import { SupportedChainId } from './chains'
export { USDC_ARBITRUM, USDC_MAINNET, USDC_OPTIMISM, USDC_POLYGON }
export const AMPL = new Token(
SupportedChainId.MAINNET,
'0xD46bA6D942050d489DBd938a2C909A5d5039A161',
@ -31,27 +46,19 @@ export const DAI_OPTIMISM = new Token(
'DAI',
'Dai stable coin'
)
export const USDC = new Token(
SupportedChainId.MAINNET,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
6,
'USDC',
'USD//C'
)
export const USDC_ARBITRUM = new Token(
SupportedChainId.ARBITRUM_ONE,
'0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8',
6,
'USDC',
'USD//C'
)
export const USDC_POLYGON = new Token(
SupportedChainId.POLYGON,
'0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
6,
'USDC',
'USD//C'
)
export const USDC: { [chainId in SupportedChainId]: Token } = {
[SupportedChainId.MAINNET]: USDC_MAINNET,
[SupportedChainId.ARBITRUM_ONE]: USDC_ARBITRUM,
[SupportedChainId.OPTIMISM]: USDC_OPTIMISM,
[SupportedChainId.ARBITRUM_RINKEBY]: USDC_ARBITRUM_RINKEBY,
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN,
[SupportedChainId.POLYGON]: USDC_POLYGON,
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI,
[SupportedChainId.GOERLI]: USDC_GÖRLI,
[SupportedChainId.RINKEBY]: USDC_RINKEBY,
[SupportedChainId.KOVAN]: USDC_KOVAN,
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN,
}
export const DAI_POLYGON = new Token(
SupportedChainId.POLYGON,
'0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
@ -73,13 +80,6 @@ export const WBTC_POLYGON = new Token(
'WBTC',
'Wrapped BTC'
)
export const USDC_OPTIMISM = new Token(
SupportedChainId.OPTIMISM,
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
6,
'USDC',
'USD//C'
)
export const USDT = new Token(
SupportedChainId.MAINNET,
'0xdAC17F958D2ee523a2206206994597C13D831ec7',
@ -296,3 +296,19 @@ export function nativeOnChain(chainId: number): NativeCurrency {
: ExtendedEther.onChain(chainId))
)
}
export const TOKEN_SHORTHANDS: { [shorthand: string]: { [chainId in SupportedChainId]?: string } } = {
USDC: {
[SupportedChainId.MAINNET]: USDC_MAINNET.address,
[SupportedChainId.ARBITRUM_ONE]: USDC_ARBITRUM.address,
[SupportedChainId.OPTIMISM]: USDC_OPTIMISM.address,
[SupportedChainId.ARBITRUM_RINKEBY]: USDC_ARBITRUM_RINKEBY.address,
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN.address,
[SupportedChainId.POLYGON]: USDC_POLYGON.address,
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI.address,
[SupportedChainId.GOERLI]: USDC_GÖRLI.address,
[SupportedChainId.RINKEBY]: USDC_RINKEBY.address,
[SupportedChainId.KOVAN]: USDC_KOVAN.address,
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN.address,
},
}

@ -2,7 +2,7 @@ import { Currency, Token } from '@uniswap/sdk-core'
import { CHAIN_INFO } from 'constants/chainInfo'
import { L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCurrencyFromMap, useTokenFromMap } from 'lib/hooks/useCurrency'
import { useCurrencyFromMap, useTokenFromMapOrNetwork } from 'lib/hooks/useCurrency'
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
import { useMemo } from 'react'
@ -159,7 +159,7 @@ export function useIsUserAddedToken(currency: Currency | undefined | null): bool
// otherwise returns the token
export function useToken(tokenAddress?: string | null): Token | null | undefined {
const tokens = useAllTokens()
return useTokenFromMap(tokens, tokenAddress)
return useTokenFromMapOrNetwork(tokens, tokenAddress)
}
export function useCurrency(currencyId?: string | null): Currency | null | undefined {

@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react-hooks'
import { CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { DAI, USDC } from 'constants/tokens'
import { DAI, USDC_MAINNET } from 'constants/tokens'
import { TradeState } from 'state/routing/types'
import { useRoutingAPITrade } from '../state/routing/useRoutingAPITrade'
@ -10,7 +10,7 @@ import { useClientSideV3Trade } from './useClientSideV3Trade'
import useDebounce from './useDebounce'
import useIsWindowVisible from './useIsWindowVisible'
const USDCAmount = CurrencyAmount.fromRawAmount(USDC, '10000')
const USDCAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '10000')
const DAIAmount = CurrencyAmount.fromRawAmount(DAI, '10000')
jest.mock('./useAutoRouterSupported')
@ -126,10 +126,10 @@ describe('#useBestV3Trade ExactOut', () => {
expectRouterMock(TradeState.INVALID)
expectClientSideMock(TradeState.VALID)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC_MAINNET)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
@ -138,17 +138,17 @@ describe('#useBestV3Trade ExactOut', () => {
expectRouterMock(TradeState.NO_ROUTE_FOUND)
expectClientSideMock(TradeState.VALID)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC_MAINNET)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
describe('when routing api is in non-error state', () => {
it('does not compute client side v3 trade if routing api is LOADING', () => {
expectRouterMock(TradeState.LOADING)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
expect(result.current).toEqual({ state: TradeState.LOADING, trade: undefined })
@ -157,7 +157,7 @@ describe('#useBestV3Trade ExactOut', () => {
it('does not compute client side v3 trade if routing api is VALID', () => {
expectRouterMock(TradeState.VALID)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
@ -166,7 +166,7 @@ describe('#useBestV3Trade ExactOut', () => {
it('does not compute client side v3 trade if routing api is SYNCING', () => {
expectRouterMock(TradeState.SYNCING)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
expect(result.current).toEqual({ state: TradeState.SYNCING, trade: undefined })
@ -178,7 +178,7 @@ describe('#useBestV3Trade ExactOut', () => {
expectRouterMock(TradeState.INVALID)
expectClientSideMock(TradeState.VALID)
renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
})
@ -187,9 +187,9 @@ describe('#useBestV3Trade ExactOut', () => {
expectRouterMock(TradeState.NO_ROUTE_FOUND)
expectClientSideMock(TradeState.VALID)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
})

@ -10,7 +10,7 @@ import { useSingleCallResult } from 'lib/hooks/multicall'
import { useMemo, useState } from 'react'
import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from '../constants/addresses'
import { DAI, UNI, USDC } from '../constants/tokens'
import { DAI, UNI, USDC_MAINNET } from '../constants/tokens'
import { useEIP2612Contract } from './useContract'
import useIsArgentWallet from './useIsArgentWallet'
@ -36,7 +36,7 @@ const PERMITTABLE_TOKENS: {
}
} = {
1: {
[USDC.address]: { type: PermitType.AMOUNT, name: 'USD Coin', version: '2' },
[USDC_MAINNET.address]: { type: PermitType.AMOUNT, name: 'USD Coin', version: '2' },
[DAI.address]: { type: PermitType.ALLOWED, name: 'Dai Stablecoin', version: '1' },
[UNI[1].address]: { type: PermitType.AMOUNT, name: 'Uniswap' },
},

@ -4,14 +4,14 @@ import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useMemo } from 'react'
import { SupportedChainId } from '../constants/chains'
import { DAI_OPTIMISM, USDC, USDC_ARBITRUM, USDC_POLYGON } from '../constants/tokens'
import { DAI_OPTIMISM, USDC_ARBITRUM, USDC_MAINNET, USDC_POLYGON } from '../constants/tokens'
import { useBestV2Trade } from './useBestV2Trade'
import { useClientSideV3Trade } from './useClientSideV3Trade'
// Stablecoin amounts used when calculating spot price for a given currency.
// The amount is large enough to filter low liquidity pairs.
export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Token> } = {
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(USDC, 100_000e6),
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(USDC_MAINNET, 100_000e6),
[SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6),
[SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(DAI_OPTIMISM, 10_000e18),
[SupportedChainId.POLYGON]: CurrencyAmount.fromRawAmount(USDC_POLYGON, 10_000e6),

@ -1,5 +1,5 @@
import { tokens } from '@uniswap/default-token-list'
import { DAI, USDC } from 'constants/tokens'
import { DAI, USDC_MAINNET } from 'constants/tokens'
import { useUpdateAtom } from 'jotai/utils'
import { useEffect } from 'react'
import { useSelect, useValue } from 'react-cosmos/fixture'
@ -41,7 +41,7 @@ function Fixture() {
none: '',
Native: 'NATIVE',
DAI: DAI.address,
USDC: USDC.address,
USDC: USDC_MAINNET.address,
}
const addressOptions = Object.keys(optionsToAddressMap)
const [defaultInput] = useSelect('defaultInputAddress', {

@ -7,9 +7,10 @@ import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react'
import { TOKEN_SHORTHANDS } from '../../constants/tokens'
import { isAddress } from '../../utils'
import { useTokenMap } from './useTokenList'
import { TokenMap } from './useTokenList'
import { supportedChainId } from '../../utils/supportedChainId'
import { TokenMap, useTokenMap } from './useTokenList'
// parse a name or symbol from a token response
const BYTES32_REGEX = /^0x[a-fA-F0-9]{64}$/
@ -28,35 +29,27 @@ function parseStringOrBytes32(str: string | undefined, bytes32: string | undefin
* Returns null if token is loading or null was passed.
* Returns undefined if tokenAddress is invalid or token does not exist.
*/
export function useTokenFromMap(tokens: TokenMap, tokenAddress?: string | null): Token | null | undefined {
export function useTokenFromNetwork(tokenAddress: string | null | undefined): Token | null | undefined {
const { chainId } = useActiveWeb3React()
const address = isAddress(tokenAddress)
const formattedAddress = isAddress(tokenAddress)
const tokenContract = useTokenContract(address ? address : undefined, false)
const tokenContractBytes32 = useBytes32TokenContract(address ? address : undefined, false)
const token: Token | undefined = address ? tokens[address] : undefined
const tokenContract = useTokenContract(formattedAddress ? formattedAddress : undefined, false)
const tokenContractBytes32 = useBytes32TokenContract(formattedAddress ? formattedAddress : undefined, false)
const tokenName = useSingleCallResult(token ? undefined : tokenContract, 'name', undefined, NEVER_RELOAD)
const tokenNameBytes32 = useSingleCallResult(
token ? undefined : tokenContractBytes32,
'name',
undefined,
NEVER_RELOAD
)
const symbol = useSingleCallResult(token ? undefined : tokenContract, 'symbol', undefined, NEVER_RELOAD)
const symbolBytes32 = useSingleCallResult(token ? undefined : tokenContractBytes32, 'symbol', undefined, NEVER_RELOAD)
const decimals = useSingleCallResult(token ? undefined : tokenContract, 'decimals', undefined, NEVER_RELOAD)
const tokenName = useSingleCallResult(tokenContract, 'name', undefined, NEVER_RELOAD)
const tokenNameBytes32 = useSingleCallResult(tokenContractBytes32, 'name', undefined, NEVER_RELOAD)
const symbol = useSingleCallResult(tokenContract, 'symbol', undefined, NEVER_RELOAD)
const symbolBytes32 = useSingleCallResult(tokenContractBytes32, 'symbol', undefined, NEVER_RELOAD)
const decimals = useSingleCallResult(tokenContract, 'decimals', undefined, NEVER_RELOAD)
return useMemo(() => {
if (token) return token
if (tokenAddress === null) return null
if (!chainId || !address) return undefined
if (typeof tokenAddress !== 'string' || !chainId || !formattedAddress) return undefined
if (decimals.loading || symbol.loading || tokenName.loading) return null
if (decimals.result) {
return new Token(
chainId,
address,
formattedAddress,
decimals.result[0],
parseStringOrBytes32(symbol.result?.[0], symbolBytes32.result?.[0], 'UNKNOWN'),
parseStringOrBytes32(tokenName.result?.[0], tokenNameBytes32.result?.[0], 'Unknown Token')
@ -64,14 +57,13 @@ export function useTokenFromMap(tokens: TokenMap, tokenAddress?: string | null):
}
return undefined
}, [
address,
formattedAddress,
chainId,
decimals.loading,
decimals.result,
symbol.loading,
symbol.result,
symbolBytes32.result,
token,
tokenAddress,
tokenName.loading,
tokenName.result,
@ -79,6 +71,20 @@ export function useTokenFromMap(tokens: TokenMap, tokenAddress?: string | null):
])
}
/**
* Returns a Token from the tokenAddress.
* Returns null if token is loading or null was passed.
* Returns undefined if tokenAddress is invalid or token does not exist.
*/
export function useTokenFromMapOrNetwork(tokens: TokenMap, tokenAddress?: string | null): Token | null | undefined {
const address = isAddress(tokenAddress)
const token: Token | undefined = address ? tokens[address] : undefined
const tokenFromNetwork = useTokenFromNetwork(token ? undefined : address ? address : undefined)
return tokenFromNetwork ?? token
}
/**
* Returns a Token from the tokenAddress.
* Returns null if token is loading or null was passed.
@ -86,7 +92,7 @@ export function useTokenFromMap(tokens: TokenMap, tokenAddress?: string | null):
*/
export function useToken(tokenAddress?: string | null): Token | null | undefined {
const tokens = useTokenMap()
return useTokenFromMap(tokens, tokenAddress)
return useTokenFromMapOrNetwork(tokens, tokenAddress)
}
/**
@ -96,8 +102,14 @@ export function useToken(tokenAddress?: string | null): Token | null | undefined
*/
export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null): Currency | null | undefined {
const nativeCurrency = useNativeCurrency()
const { chainId } = useActiveWeb3React()
const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH')
const token = useTokenFromMap(tokens, isNative ? undefined : currencyId)
const shorthandMatchAddress = useMemo(() => {
const chain = supportedChainId(chainId)
return chain && currencyId ? TOKEN_SHORTHANDS[currencyId.toUpperCase()]?.[chain] : undefined
}, [chainId, currencyId])
const token = useTokenFromMapOrNetwork(tokens, isNative ? undefined : shorthandMatchAddress ?? currencyId)
if (currencyId === null || currencyId === undefined) return currencyId

@ -33,6 +33,7 @@ import { ArrowWrapper, SwapCallbackError, Wrapper } from '../../components/swap/
import SwapHeader from '../../components/swap/SwapHeader'
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import TokenWarningModal from '../../components/TokenWarningModal'
import { TOKEN_SHORTHANDS } from '../../constants/tokens'
import { useAllTokens, useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApprovalOptimizedTrade, useApproveCallbackFromTrade } from '../../hooks/useApproveCallback'
import useENSAddress from '../../hooks/useENSAddress'
@ -54,6 +55,7 @@ import { LinkStyledButton, ThemedText } from '../../theme'
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { warningSeverity } from '../../utils/prices'
import { supportedChainId } from '../../utils/supportedChainId'
import AppBody from '../AppBody'
const AlertWrapper = styled.div`
@ -62,13 +64,13 @@ const AlertWrapper = styled.div`
`
export default function Swap({ history }: RouteComponentProps) {
const { account } = useActiveWeb3React()
const { account, chainId } = useActiveWeb3React()
const loadedUrlParams = useDefaultsFromURLSearch()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId),
useCurrency(loadedUrlParams?.[Field.INPUT]?.currencyId),
useCurrency(loadedUrlParams?.[Field.OUTPUT]?.currencyId),
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const urlLoadedTokens: Token[] = useMemo(
@ -84,10 +86,20 @@ export default function Swap({ history }: RouteComponentProps) {
const importTokensNotInDefault = useMemo(
() =>
urlLoadedTokens &&
urlLoadedTokens.filter((token: Token) => {
return !Boolean(token.address in defaultTokens)
}),
[defaultTokens, urlLoadedTokens]
urlLoadedTokens
.filter((token: Token) => {
return !Boolean(token.address in defaultTokens)
})
.filter((token: Token) => {
// Any token addresses that are loaded from the shorthands map do not need to show the import URL
const supported = supportedChainId(chainId)
if (!supported) return true
return !Object.keys(TOKEN_SHORTHANDS).some((shorthand) => {
const shorthandTokenAddress = TOKEN_SHORTHANDS[shorthand][supported]
return shorthandTokenAddress && shorthandTokenAddress === token.address
})
}),
[chainId, defaultTokens, urlLoadedTokens]
)
const theme = useContext(ThemeContext)

@ -10,7 +10,7 @@ import { NEVER_RELOAD, useMultipleContractSingleData } from 'lib/hooks/multicall
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { ReactNode, useMemo } from 'react'
import { DAI, UNI, USDC, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import { DAI, UNI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
const { abi: STAKING_REWARDS_ABI } = StakingRewardsJson
const STAKING_REWARDS_INTERFACE = new Interface(STAKING_REWARDS_ABI)
@ -31,7 +31,7 @@ export const STAKING_REWARDS_INFO: {
stakingRewardAddress: '0xa1484C3aa22a66C62b77E0AE78E15258bd0cB711',
},
{
tokens: [WRAPPED_NATIVE_CURRENCY[1], USDC],
tokens: [WRAPPED_NATIVE_CURRENCY[1], USDC_MAINNET],
stakingRewardAddress: '0x7FBa4B8Dc5E7616e59622806932DBea72537A56b',
},
{

@ -27,8 +27,8 @@ describe('hooks', () => {
queryParametersToSwapState(parse('?outputCurrency=invalid', { parseArrays: false, ignoreQueryPrefix: true }))
).toEqual({
[Field.INPUT]: { currencyId: 'ETH' },
[Field.OUTPUT]: { currencyId: null },
typedValue: '',
[Field.OUTPUT]: { currencyId: 'USDC' },
typedValue: '1',
independentField: Field.INPUT,
recipient: null,
})

@ -5,11 +5,12 @@ import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
import { useBestTrade } from 'hooks/useBestTrade'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { ParsedQs } from 'qs'
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { ReactNode, useCallback, useEffect, useMemo } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { InterfaceTrade, TradeState } from 'state/routing/types'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import { TOKEN_SHORTHANDS } from '../../constants/tokens'
import { useCurrency } from '../../hooks/Tokens'
import useENS from '../../hooks/useENS'
import useParsedQueryString from '../../hooks/useParsedQueryString'
@ -185,11 +186,13 @@ export function useDerivedSwapInfo(): {
)
}
function parseCurrencyFromURLParameter(urlParam: any): string {
function parseCurrencyFromURLParameter(urlParam: ParsedQs[string]): string {
if (typeof urlParam === 'string') {
const valid = isAddress(urlParam)
if (valid) return valid
if (urlParam.toUpperCase() === 'ETH') return 'ETH'
const upper = urlParam.toUpperCase()
if (upper === 'ETH') return 'ETH'
if (upper in TOKEN_SHORTHANDS) return upper
}
return ''
}
@ -216,9 +219,14 @@ function validatedRecipient(recipient: any): string | null {
export function queryParametersToSwapState(parsedQs: ParsedQs): SwapState {
let inputCurrency = parseCurrencyFromURLParameter(parsedQs.inputCurrency)
let outputCurrency = parseCurrencyFromURLParameter(parsedQs.outputCurrency)
if (inputCurrency === '' && outputCurrency === '') {
// default to ETH input
let typedValue = parseTokenAmountURLParameter(parsedQs.exactAmount)
const independentField = parseIndependentFieldURLParameter(parsedQs.exactField)
if (inputCurrency === '' && outputCurrency === '' && typedValue === '' && independentField === Field.INPUT) {
// Defaults to 1 ETH -> USDC
inputCurrency = 'ETH'
outputCurrency = 'USDC'
typedValue = '1'
} else if (inputCurrency === outputCurrency) {
// clear output if identical
outputCurrency = ''
@ -233,42 +241,39 @@ export function queryParametersToSwapState(parsedQs: ParsedQs): SwapState {
[Field.OUTPUT]: {
currencyId: outputCurrency === '' ? null : outputCurrency ?? null,
},
typedValue: parseTokenAmountURLParameter(parsedQs.exactAmount),
independentField: parseIndependentFieldURLParameter(parsedQs.exactField),
typedValue,
independentField,
recipient,
}
}
// updates the swap state to use the defaults for a given network
export function useDefaultsFromURLSearch():
| { inputCurrencyId: string | undefined; outputCurrencyId: string | undefined }
| undefined {
export function useDefaultsFromURLSearch(): SwapState {
const { chainId } = useActiveWeb3React()
const dispatch = useAppDispatch()
const parsedQs = useParsedQueryString()
const [result, setResult] = useState<
{ inputCurrencyId: string | undefined; outputCurrencyId: string | undefined } | undefined
>()
const parsedSwapState = useMemo(() => {
return queryParametersToSwapState(parsedQs)
}, [parsedQs])
useEffect(() => {
if (!chainId) return
const parsed = queryParametersToSwapState(parsedQs)
const inputCurrencyId = parsed[Field.INPUT].currencyId ?? undefined
const outputCurrencyId = parsed[Field.OUTPUT].currencyId ?? undefined
const inputCurrencyId = parsedSwapState[Field.INPUT].currencyId ?? undefined
const outputCurrencyId = parsedSwapState[Field.OUTPUT].currencyId ?? undefined
dispatch(
replaceSwapState({
typedValue: parsed.typedValue,
field: parsed.independentField,
typedValue: parsedSwapState.typedValue,
field: parsedSwapState.independentField,
inputCurrencyId,
outputCurrencyId,
recipient: parsed.recipient,
recipient: parsedSwapState.recipient,
})
)
setResult({ inputCurrencyId, outputCurrencyId })
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch, chainId])
return result
return parsedSwapState
}

@ -4,8 +4,8 @@ import { SupportedChainId } from '../constants/chains'
* 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
*/
export function supportedChainId(chainId: number): number | undefined {
if (chainId in SupportedChainId) {
export function supportedChainId(chainId: number | undefined): SupportedChainId | undefined {
if (typeof chainId === 'number' && chainId in SupportedChainId) {
return chainId
}
return undefined