diff --git a/cypress/integration/swap.test.ts b/cypress/integration/swap.test.ts index 46216c2f44..3fb042ffc9 100644 --- a/cypress/integration/swap.test.ts +++ b/cypress/integration/swap.test.ts @@ -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', () => { diff --git a/src/components/PositionListItem/index.tsx b/src/components/PositionListItem/index.tsx index 97d8df54f8..e8916e4066 100644 --- a/src/components/PositionListItem/index.tsx +++ b/src/components/PositionListItem/index.tsx @@ -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(), diff --git a/src/components/RoutingDiagram/RoutingDiagram.test.tsx b/src/components/RoutingDiagram/RoutingDiagram.test.tsx index 7b80468ab1..5f6e9338e9 100644 --- a/src/components/RoutingDiagram/RoutingDiagram.test.tsx +++ b/src/components/RoutingDiagram/RoutingDiagram.test.tsx @@ -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() + const { asFragment } = render() expect(asFragment()).toMatchSnapshot() }) it('renders single route', () => { - const { asFragment } = render() + const { asFragment } = render() expect(asFragment()).toMatchSnapshot() }) it('renders multi route', () => { - const { asFragment } = render() + const { asFragment } = render() expect(asFragment()).toMatchSnapshot() }) diff --git a/src/constants/routing.ts b/src/constants/routing.ts index 66097a4133..9e06d5c157 100644 --- a/src/constants/routing.ts +++ b/src/constants/routing.ts @@ -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], ], } diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index bee5ac6f92..d17f32f403 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -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, + }, +} diff --git a/src/hooks/Tokens.ts b/src/hooks/Tokens.ts index 4b2268f6aa..bed5ba2c8b 100644 --- a/src/hooks/Tokens.ts +++ b/src/hooks/Tokens.ts @@ -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 { diff --git a/src/hooks/useBestTrade.test.ts b/src/hooks/useBestTrade.test.ts index b15c423d57..0cf1759131 100644 --- a/src/hooks/useBestTrade.test.ts +++ b/src/hooks/useBestTrade.test.ts @@ -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 }) }) }) diff --git a/src/hooks/useERC20Permit.ts b/src/hooks/useERC20Permit.ts index d8ccfe5773..71c0c750aa 100644 --- a/src/hooks/useERC20Permit.ts +++ b/src/hooks/useERC20Permit.ts @@ -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' }, }, diff --git a/src/hooks/useUSDCPrice.ts b/src/hooks/useUSDCPrice.ts index 03e60d2340..540f8e39b1 100644 --- a/src/hooks/useUSDCPrice.ts +++ b/src/hooks/useUSDCPrice.ts @@ -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 } = { - [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), diff --git a/src/lib/components/Swap/Swap.fixture.tsx b/src/lib/components/Swap/Swap.fixture.tsx index 07036b9ea2..e7b5e7403e 100644 --- a/src/lib/components/Swap/Swap.fixture.tsx +++ b/src/lib/components/Swap/Swap.fixture.tsx @@ -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', { diff --git a/src/lib/hooks/useCurrency.ts b/src/lib/hooks/useCurrency.ts index fd4e992ffb..8dd52f6cdb 100644 --- a/src/lib/hooks/useCurrency.ts +++ b/src/lib/hooks/useCurrency.ts @@ -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 diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index a626cbe82a..d6384b4b0b 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -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(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) diff --git a/src/state/stake/hooks.tsx b/src/state/stake/hooks.tsx index fd96779648..c4f14f52c8 100644 --- a/src/state/stake/hooks.tsx +++ b/src/state/stake/hooks.tsx @@ -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', }, { diff --git a/src/state/swap/hooks.test.ts b/src/state/swap/hooks.test.ts index 22718c5e55..37be9d2528 100644 --- a/src/state/swap/hooks.test.ts +++ b/src/state/swap/hooks.test.ts @@ -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, }) diff --git a/src/state/swap/hooks.tsx b/src/state/swap/hooks.tsx index b91da25691..88011e6c5c 100644 --- a/src/state/swap/hooks.tsx +++ b/src/state/swap/hooks.tsx @@ -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 } diff --git a/src/utils/supportedChainId.ts b/src/utils/supportedChainId.ts index d3fef188ee..4e8cd7fff0 100644 --- a/src/utils/supportedChainId.ts +++ b/src/utils/supportedChainId.ts @@ -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