Skip to content

Commit

Permalink
feat: add chainId (network) as url parameter (#3057)
Browse files Browse the repository at this point in the history
* read from query param and change networks if necessary

* dont open network selector menu on url param change

* prompt network change when url changes

* keep url, network in sync

* use chain name instead of id in url param

* only prompt network switch if url chain doesnt match
  • Loading branch information
tinaszheng authored Jan 24, 2022
1 parent bbdb5f3 commit 3153db9
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 11 deletions.
84 changes: 73 additions & 11 deletions src/components/Header/NetworkSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { Trans } from '@lingui/macro'
import { CHAIN_INFO } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { CHAIN_IDS_TO_NAMES, SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useCallback, useRef } from 'react'
import useParsedQueryString from 'hooks/useParsedQueryString'
import usePrevious from 'hooks/usePrevious'
import { ParsedQs } from 'qs'
import { useCallback, useEffect, useRef } from 'react'
import { ArrowDownCircle, ChevronDown } from 'react-feather'
import { useHistory } from 'react-router-dom'
import { useModalOpen, useToggleModal } from 'state/application/hooks'
import { addPopup, ApplicationModal } from 'state/application/reducer'
import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { replaceURLParam } from 'utils/routes'

import { useAppDispatch } from '../../state/hooks'
import { switchToNetwork } from '../../utils/switchToNetwork'
Expand Down Expand Up @@ -211,31 +216,88 @@ function Row({
return rowContent
}

const getParsedChainId = (parsedQs?: ParsedQs) => {
const chain = parsedQs?.chain
if (!chain || typeof chain !== 'string') return { urlChain: undefined, urlChainId: undefined }

return { urlChain: chain.toLowerCase(), urlChainId: getChainIdFromName(chain) }
}

const getChainIdFromName = (name: string) => {
const entry = Object.entries(CHAIN_IDS_TO_NAMES).find(([_, n]) => n === name)
const chainId = entry?.[0]
return chainId ? parseInt(chainId) : undefined
}

const getChainNameFromId = (id: string | number) => {
// casting here may not be right but fine to return undefined if it's not a supported chain ID
return CHAIN_IDS_TO_NAMES[id as SupportedChainId] || ''
}

export default function NetworkSelector() {
const { chainId, library } = useActiveWeb3React()
const parsedQs = useParsedQueryString()
const { urlChain, urlChainId } = getParsedChainId(parsedQs)
const prevChainId = usePrevious(chainId)
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.NETWORK_SELECTOR)
const toggle = useToggleModal(ApplicationModal.NETWORK_SELECTOR)
useOnClickOutside(node, open ? toggle : undefined)

const history = useHistory()

const info = chainId ? CHAIN_INFO[chainId] : undefined

const dispatch = useAppDispatch()

const handleRowClick = useCallback(
(targetChain: number) => {
const handleChainSwitch = useCallback(
(targetChain: number, skipToggle?: boolean) => {
if (!library) return
switchToNetwork({ library, chainId: targetChain })
.then(() => toggle())
.then(() => {
if (!skipToggle) {
toggle()
}
history.replace({
search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(targetChain)),
})
})
.catch((error) => {
console.error('Failed to switch networks', error)
toggle()

// we want app network <-> chainId param to be in sync, so if user changes the network by changing the URL
// but the request fails, revert the URL back to current chainId
if (chainId) {
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
}

if (!skipToggle) {
toggle()
}

dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
})
},
[dispatch, library, toggle]
[dispatch, library, toggle, history, chainId]
)

useEffect(() => {
// when network change originates from wallet or dropdown selector, just update URL
if (chainId && chainId !== prevChainId) {
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
// otherwise assume network change originates from URL
} else if (urlChainId && urlChainId !== chainId) {
handleChainSwitch(urlChainId, true)
}
}, [chainId, urlChainId, prevChainId, handleChainSwitch, history])

// set chain parameter on initial load if not there
useEffect(() => {
if (chainId && !urlChainId) {
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
}
}, [chainId, history, urlChainId, urlChain])

if (!chainId || !info || !library) {
return null
}
Expand All @@ -252,10 +314,10 @@ export default function NetworkSelector() {
<FlyoutHeader>
<Trans>Select a network</Trans>
</FlyoutHeader>
<Row onSelectChain={handleRowClick} targetChain={SupportedChainId.MAINNET} />
<Row onSelectChain={handleRowClick} targetChain={SupportedChainId.POLYGON} />
<Row onSelectChain={handleRowClick} targetChain={SupportedChainId.OPTIMISM} />
<Row onSelectChain={handleRowClick} targetChain={SupportedChainId.ARBITRUM_ONE} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.MAINNET} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.POLYGON} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.OPTIMISM} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.ARBITRUM_ONE} />
</FlyoutMenu>
)}
</SelectorWrapper>
Expand Down
14 changes: 14 additions & 0 deletions src/constants/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ export enum SupportedChainId {
POLYGON_MUMBAI = 80001,
}

export const CHAIN_IDS_TO_NAMES = {
[SupportedChainId.MAINNET]: 'mainnet',
[SupportedChainId.ROPSTEN]: 'ropsten',
[SupportedChainId.RINKEBY]: 'rinkeby',
[SupportedChainId.GOERLI]: 'goerli',
[SupportedChainId.KOVAN]: 'kovan',
[SupportedChainId.POLYGON]: 'polygon',
[SupportedChainId.POLYGON_MUMBAI]: 'polygon_mumbai',
[SupportedChainId.ARBITRUM_ONE]: 'arbitrum',
[SupportedChainId.ARBITRUM_RINKEBY]: 'arbitrum_rinkeby',
[SupportedChainId.OPTIMISM]: 'optimism',
[SupportedChainId.OPTIMISTIC_KOVAN]: 'optimistic_kovan',
}

/**
* Array of all the supported chain IDs
*/
Expand Down
5 changes: 5 additions & 0 deletions src/utils/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const replaceURLParam = (search: string, param: string, newValue: string) => {
const searchParams = new URLSearchParams(search)
searchParams.set(param, newValue)
return searchParams.toString()
}

0 comments on commit 3153db9

Please sign in to comment.