Skip to content

Commit

Permalink
fix: the default chainId is picked fromdefaultChainId (#281)
Browse files Browse the repository at this point in the history
* check provider for default check

* check priority wallet

* group default input token

* default the chain id to mainnet

* switch behavior on connector change

* output token defaults and first test

* initial render tests

* add tests and update eslint

* add default chain id to default

* style(lint): lint action with ESLint

* update tests with defaultChainId on TokenDefaults

* static import and revert eslint

* cast type for type check pass

* useTokens to accept chain id

* skip network

* update param name

* update switch var names

* no default for setToDefaults

* define skipNetwork

* pull in supported chain id

Co-authored-by: Lint Action <lint-action@uniswap.org>
  • Loading branch information
dannythedawger and Lint Action authored Oct 21, 2022
1 parent 9ebc588 commit 5479440
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 48 deletions.
127 changes: 127 additions & 0 deletions src/hooks/swap/useSyncTokenDefaults.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { TradeType } from '@uniswap/sdk-core'
import { SupportedChainId } from 'constants/chains'
import { DAI_POLYGON, nativeOnChain } from 'constants/tokens'
import { useAtomValue } from 'jotai/utils'
import { Field, stateAtom, Swap, swapAtom } from 'state/swap'
import { renderHook } from 'test'

import { USDC_MAINNET } from '../../constants/tokens'
import useSyncTokenDefaults, { TokenDefaults } from './useSyncTokenDefaults'

const MOCK_DAI_POLYGON = DAI_POLYGON
const MOCK_USDC_MAINNET = USDC_MAINNET

const INITIAL_SWAP: Swap = {
type: TradeType.EXACT_INPUT,
amount: '10',
[Field.INPUT]: MOCK_USDC_MAINNET,
[Field.OUTPUT]: MOCK_DAI_POLYGON,
}

const TOKEN_DEFAULTS: TokenDefaults = {
defaultInputAmount: 10,
defaultInputTokenAddress: 'NATIVE',
defaultOutputTokenAddress: 'NATIVE',
}

jest.mock('@web3-react/core', () => {
const { SupportedChainId } = jest.requireActual('constants/chains')

return {
useWeb3React: () => ({
chainId: SupportedChainId.MAINNET,
connector: {},
}),
}
})

jest.mock('../useTokenList', () => {
return {
useIsTokenListLoaded: () => true,
}
})

jest.mock('hooks/useCurrency', () => {
return {
useToken: () => MOCK_DAI_POLYGON,
}
})

describe('useSyncTokenDefaults', () => {
it('syncs to default chainId on initial render if defaultChainId is provided', () => {
const { rerender } = renderHook(
() => {
useSyncTokenDefaults({ ...TOKEN_DEFAULTS, defaultChainId: SupportedChainId.POLYGON })
},
{
initialAtomValues: [[stateAtom, INITIAL_SWAP]],
}
)

const { result } = rerender(() => useAtomValue(swapAtom))
expect(result.current).toMatchObject({
...INITIAL_SWAP,
INPUT: nativeOnChain(SupportedChainId.POLYGON),
OUTPUT: nativeOnChain(SupportedChainId.POLYGON),
})
})

it('does not sync to default chainId on initial render if defaultChainId is not provided', () => {
const { rerender } = renderHook(
() => {
useSyncTokenDefaults(TOKEN_DEFAULTS)
},
{
initialAtomValues: [[stateAtom, INITIAL_SWAP]],
}
)

const { result } = rerender(() => useAtomValue(swapAtom))
expect(result.current).toMatchObject({
...INITIAL_SWAP,
INPUT: nativeOnChain(SupportedChainId.MAINNET),
OUTPUT: nativeOnChain(SupportedChainId.MAINNET),
})
})

it('syncs to default non NATIVE tokens of default chainId on initial render if defaultChainId is provided', () => {
const { rerender } = renderHook(
() => {
useSyncTokenDefaults({
...TOKEN_DEFAULTS,
defaultInputTokenAddress: DAI_POLYGON.address,
defaultOutputTokenAddress: DAI_POLYGON.address,
defaultChainId: SupportedChainId.POLYGON,
})
},
{
initialAtomValues: [[stateAtom, INITIAL_SWAP]],
}
)

const { result } = rerender(() => useAtomValue(swapAtom))
expect(result.current).toMatchObject({
...INITIAL_SWAP,
INPUT: DAI_POLYGON,
OUTPUT: DAI_POLYGON,
})
})

it('syncs to non NATIVE tokens of chainId on initial render if defaultChainId is not provided', () => {
const { rerender } = renderHook(
() => {
useSyncTokenDefaults(TOKEN_DEFAULTS)
},
{
initialAtomValues: [[stateAtom, INITIAL_SWAP]],
}
)

const { result } = rerender(() => useAtomValue(swapAtom))
expect(result.current).toMatchObject({
...INITIAL_SWAP,
INPUT: nativeOnChain(SupportedChainId.MAINNET),
OUTPUT: nativeOnChain(SupportedChainId.MAINNET),
})
})
})
101 changes: 63 additions & 38 deletions src/hooks/swap/useSyncTokenDefaults.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Currency, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { SupportedChainId } from 'constants/chains'
import { nativeOnChain } from 'constants/tokens'
import { useToken } from 'hooks/useCurrency'
import useNativeCurrency from 'hooks/useNativeCurrency'
import { useUpdateAtom } from 'jotai/utils'
import { useCallback, useEffect, useRef } from 'react'
import { useCallback, useEffect, useMemo, useRef } from 'react'
import { Field, Swap, swapAtom } from 'state/swap'

import useOnSupportedNetwork from '../useOnSupportedNetwork'
Expand All @@ -17,68 +18,92 @@ export interface TokenDefaults {
defaultInputAmount?: number | string
defaultOutputTokenAddress?: DefaultAddress
defaultOutputAmount?: number | string
defaultChainId?: SupportedChainId
}

function useDefaultToken(
defaultAddress: DefaultAddress | undefined,
chainId: number | undefined
chainId: number | undefined,
defaultToNative: boolean
): Currency | undefined {
let address = undefined
let address: undefined | string = undefined
if (typeof defaultAddress === 'string') {
address = defaultAddress
} else if (typeof defaultAddress === 'object' && chainId) {
address = defaultAddress[chainId]
}
const token = useToken(address)
const token = useToken(address, chainId)
const onSupportedNetwork = useOnSupportedNetwork(chainId)

const onSupportedNetwork = useOnSupportedNetwork()
return useMemo(() => {
// Only use native currency if chain ID is in supported chains. ExtendedEther will error otherwise.
if (chainId && onSupportedNetwork && (address === 'NATIVE' || (!token && defaultToNative))) {
return nativeOnChain(chainId)
}

// Only use native currency if chain ID is in supported chains. ExtendedEther will error otherwise.
if (chainId && address === 'NATIVE' && onSupportedNetwork) {
return nativeOnChain(chainId)
}
return token ?? undefined
return token ?? undefined
}, [address, chainId, defaultToNative, onSupportedNetwork, token])
}

export default function useSyncTokenDefaults({
defaultInputTokenAddress,
defaultInputAmount,
defaultOutputTokenAddress,
defaultOutputAmount,
defaultChainId,
}: TokenDefaults) {
const lastChainId = useRef<number | undefined>(undefined)
const lastConnector = useRef<Connector | undefined>(undefined)
const updateSwap = useUpdateAtom(swapAtom)
const { chainId } = useWeb3React()
const onSupportedNetwork = useOnSupportedNetwork()
const nativeCurrency = useNativeCurrency()
const defaultOutputToken = useDefaultToken(defaultOutputTokenAddress, chainId)
const defaultInputToken =
useDefaultToken(defaultInputTokenAddress, chainId) ??
// Default the input token to the native currency if it is not the output token.
(defaultOutputToken !== nativeCurrency && onSupportedNetwork ? nativeCurrency : undefined)
const { chainId, connector } = useWeb3React()

const setToDefaults = useCallback(() => {
const defaultSwapState: Swap = {
amount: '',
[Field.INPUT]: defaultInputToken,
[Field.OUTPUT]: defaultOutputToken,
type: TradeType.EXACT_INPUT,
}
if (defaultInputToken && defaultInputAmount) {
defaultSwapState.amount = defaultInputAmount.toString()
} else if (defaultOutputToken && defaultOutputAmount) {
defaultSwapState.type = TradeType.EXACT_OUTPUT
defaultSwapState.amount = defaultOutputAmount.toString()
}
updateSwap((swap) => ({ ...swap, ...defaultSwapState }))
}, [defaultInputAmount, defaultInputToken, defaultOutputAmount, defaultOutputToken, updateSwap])
const defaultOutputToken = useDefaultToken(defaultOutputTokenAddress, chainId, false)
const defaultChainIdOutputToken = useDefaultToken(defaultOutputTokenAddress, defaultChainId, false)

const defaultInputToken = useDefaultToken(defaultInputTokenAddress, chainId, true)
const defaultChainIdInputToken = useDefaultToken(defaultInputTokenAddress, defaultChainId, true)

const setToDefaults = useCallback(
(shouldUseDefaultChainId) => {
const defaultSwapState: Swap = {
amount: '',
[Field.INPUT]: shouldUseDefaultChainId ? defaultChainIdInputToken : defaultInputToken,
[Field.OUTPUT]: shouldUseDefaultChainId ? defaultChainIdOutputToken : defaultOutputToken,
type: TradeType.EXACT_INPUT,
}

if (defaultInputToken && defaultInputAmount) {
defaultSwapState.amount = defaultInputAmount.toString()
} else if (defaultOutputToken && defaultOutputAmount) {
defaultSwapState.type = TradeType.EXACT_OUTPUT
defaultSwapState.amount = defaultOutputAmount.toString()
}
updateSwap((swap) => ({ ...swap, ...defaultSwapState }))
},
[
defaultChainIdInputToken,
defaultInputToken,
defaultChainIdOutputToken,
defaultOutputToken,
defaultInputAmount,
defaultOutputAmount,
updateSwap,
]
)

const lastChainId = useRef<number | undefined>(undefined)
const isTokenListLoaded = useIsTokenListLoaded()

useEffect(() => {
const shouldSync = isTokenListLoaded && chainId && chainId !== lastChainId.current
const isChainSwitched = chainId && chainId !== lastChainId.current
const isConnectorSwitched = connector && connector !== lastConnector.current
const shouldSync = isTokenListLoaded && (isChainSwitched || isConnectorSwitched)
const shouldUseDefaultChainId = isConnectorSwitched && defaultChainId

if (shouldSync) {
setToDefaults()
setToDefaults(shouldUseDefaultChainId)

lastChainId.current = chainId
lastConnector.current = connector
}
}, [isTokenListLoaded, chainId, setToDefaults])
}, [isTokenListLoaded, chainId, setToDefaults, connector, defaultChainId])
}
18 changes: 13 additions & 5 deletions src/hooks/useCurrency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useMemo } from 'react'
import { isAddress } from 'utils'
import { supportedChainId } from 'utils/supportedChainId'

import { SupportedChainId } from '..'
import { TokenMap, useTokenMap } from './useTokenList'

// parse a name or symbol from a token response
Expand Down Expand Up @@ -76,23 +77,30 @@ export function useTokenFromNetwork(tokenAddress: string | null | undefined): To
* 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 {
export function useTokenFromMapOrNetwork(
tokens: TokenMap,
tokenAddress?: string | null,
skipNetwork = false
): 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
return skipNetwork ? token : tokenFromNetwork || token
}

/**
* 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 useToken(tokenAddress?: string | null): Token | null | undefined {
const tokens = useTokenMap()
return useTokenFromMapOrNetwork(tokens, tokenAddress)
export function useToken(tokenAddress?: string | null, chainId?: SupportedChainId): Token | null | undefined {
const { chainId: activeChainId } = useWeb3React()

const tokens = useTokenMap(chainId)
const skipNetwork = chainId && chainId !== activeChainId
return useTokenFromMapOrNetwork(tokens, tokenAddress, skipNetwork)
}

/**
Expand Down
9 changes: 6 additions & 3 deletions src/hooks/useOnSupportedNetwork.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useWeb3React } from '@web3-react/core'
import { ALL_SUPPORTED_CHAIN_IDS } from 'constants/chains'
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
import { useMemo } from 'react'

function useOnSupportedNetwork() {
const { chainId } = useWeb3React()
function useOnSupportedNetwork(chainId?: SupportedChainId) {
const { chainId: activeChainId } = useWeb3React()

chainId = chainId || activeChainId

return useMemo(() => Boolean(chainId && ALL_SUPPORTED_CHAIN_IDS.includes(chainId)), [chainId])
}

Expand Down
8 changes: 6 additions & 2 deletions src/hooks/useTokenList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Token } from '@uniswap/sdk-core'
import { TokenInfo, TokenList } from '@uniswap/token-lists'
import { useWeb3React } from '@web3-react/core'
import { SupportedChainId } from 'constants/chains'
import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import resolveENSContentHash from 'utils/resolveENSContentHash'
Expand Down Expand Up @@ -41,8 +42,11 @@ export default function useTokenList(): WrappedTokenInfo[] {

export type TokenMap = { [address: string]: Token }

export function useTokenMap(): TokenMap {
const { chainId } = useWeb3React()
export function useTokenMap(chainId?: SupportedChainId): TokenMap {
const { chainId: activeChainId } = useWeb3React()

chainId = chainId || activeChainId

const chainTokenMap = useChainTokenMapContext()
const tokenMap = chainId && chainTokenMap?.[chainId]
return useMemo(() => {
Expand Down

1 comment on commit 5479440

@vercel
Copy link

@vercel vercel bot commented on 5479440 Oct 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

widgets – ./

widgets-git-main-uniswap.vercel.app
widgets-uniswap.vercel.app
widgets-seven-tau.vercel.app

Please sign in to comment.