Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add useNetworks and useNetworksRelationship hooks #1297

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8669576
feat: Add useNetworks and useNetworksRelationship hooks
chrstph-dvx Nov 16, 2023
3db047b
Update packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts
chrstph-dvx Nov 20, 2023
995e18c
Update packages/arb-token-bridge-ui/src/hooks/useNetworks.ts
chrstph-dvx Nov 20, 2023
a573c1c
Merge branch 'master' into 1296-add-usenetworks-and-usenetworksrelati…
chrstph-dvx Nov 20, 2023
8ac459e
Update hooks
chrstph-dvx Nov 20, 2023
510f47d
use kebab case network names
chrstph-dvx Nov 21, 2023
e7dd2db
Add support for orbit chain
chrstph-dvx Nov 22, 2023
27c6e56
Merge branch 'master' into 1296-add-usenetworks-and-usenetworksrelati…
chrstph-dvx Nov 22, 2023
c5535df
Add cases to handle invalid chainId in useNetwork
chrstph-dvx Nov 22, 2023
de64492
Add one check if chains are not partners
chrstph-dvx Nov 22, 2023
fa0376d
Add customChain Ids to the list of supported chain
chrstph-dvx Nov 22, 2023
53980ae
Merge branch 'master' into 1296-add-usenetworks-and-usenetworksrelati…
chrstph-dvx Nov 22, 2023
906dd75
Merge branch 'master' into 1296-add-usenetworks-and-usenetworksrelati…
chrstph-dvx Nov 30, 2023
6315da0
Allow undefined sourceChain in useNetworks
chrstph-dvx Nov 30, 2023
a372977
Add unit tests for chainParam encoder
chrstph-dvx Nov 30, 2023
15b4a2d
Fix typing
chrstph-dvx Nov 30, 2023
8131db1
Cast encodeChainQueryParam result to string
chrstph-dvx Nov 30, 2023
f4f221b
Add tests for custom orbit chain for sanitizeQueryParams
chrstph-dvx Nov 30, 2023
39deb03
Simplify useArbQueryParams
chrstph-dvx Dec 4, 2023
0a732e4
Fix TS error in ExternalLink
chrstph-dvx Dec 4, 2023
e40c4d7
Mock localstorage
chrstph-dvx Dec 4, 2023
55adab1
Fix mock
chrstph-dvx Dec 4, 2023
4c97724
Undo change in ExternalLink
chrstph-dvx Dec 4, 2023
2726d24
Undo changes in wagmi setup
chrstph-dvx Dec 4, 2023
2a6c5c5
Simplify getPartnerChainsForChainId
chrstph-dvx Dec 4, 2023
bf7e0e8
Update type for ChainQueryParam
chrstph-dvx Dec 4, 2023
49de76a
Remove network in useNetworksRelationShip
chrstph-dvx Dec 4, 2023
5bbb6d5
Simplify useNetworks
chrstph-dvx Dec 5, 2023
668ad95
Merge branch 'master' into 1296-add-usenetworks-and-usenetworksrelati…
chrstph-dvx Dec 5, 2023
8ee28bc
Merge branch 'master' into 1296-add-usenetworks-and-usenetworksrelati…
chrstph-dvx Dec 6, 2023
1e99be8
Add number to decodeChainQueryParam return result
chrstph-dvx Dec 6, 2023
2fbaf61
Undo change in wagmi setup
chrstph-dvx Dec 6, 2023
45d1a19
Rework useNetworks to handle invalid chain ids
chrstph-dvx Dec 6, 2023
aabee9b
Replace toBe with toEqual
chrstph-dvx Dec 6, 2023
a02ad33
Rename variables in useNetworks setter
chrstph-dvx Dec 6, 2023
4d5ed4e
Merge branch 'master' into 1296-add-usenetworks-and-usenetworksrelati…
chrstph-dvx Dec 7, 2023
c1df715
Update isValidNumber
chrstph-dvx Dec 7, 2023
41d7f86
Add return type to encodeChainQueryParam
chrstph-dvx Dec 7, 2023
0ff42ce
Add support for Nova
chrstph-dvx Dec 7, 2023
5937651
Add support for ArbitrumOne
chrstph-dvx Dec 7, 2023
caf0686
Update tests
chrstph-dvx Dec 7, 2023
d53d416
Group tests
chrstph-dvx Dec 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/arb-token-bridge-ui/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ const transformNodeModules = [
// The following are dependencies for query-string (https://github.com/sindresorhus/query-string/blob/main/package.json)
'decode-uri-component',
'split-on-first',
'filter-obj'
'filter-obj',
// wagmi
'@wagmi',
'wagmi'
]

module.exports = async function () {
Expand Down
chrstph-dvx marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

Don't see a test case that covers passing in one unsupported chain id (or both)

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @jest-environment jsdom
*/
import { sanitizeQueryParams } from '../useNetworks'

describe('sanitizeQueryParams', () => {
it('sets the default values for `from` and `to` when both `from` and `to` are `undefined`', () => {
const result = sanitizeQueryParams({ from: undefined, to: undefined })
expect(result).toEqual({ from: 'ethereum', to: 'arbitrumOne' })
})

it('sets the value for `to` to the partner chain of `from` when `to` is `undefined`', () => {
const result = sanitizeQueryParams({ from: 'ethereum', to: undefined })
expect(result).toEqual({ from: 'ethereum', to: 'arbitrumOne' })
})

it('sets the value for `from` to the partner chain of `to` when `from` is `undefined`', () => {
const result = sanitizeQueryParams({ from: undefined, to: 'arbitrumNova' })
expect(result).toEqual({ from: 'ethereum', to: 'arbitrumNova' })
})

it('sets the value for `to` to the partner chain of `from` when `to` is an invalid value', () => {
const result = sanitizeQueryParams({ from: 'goerli', to: 'arbitrumOne' })
expect(result).toEqual({ from: 'goerli', to: 'arbitrumGoerli' })
})
})
24 changes: 24 additions & 0 deletions packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import {
withDefault
} from 'use-query-params'

import {
ChainQueryParam,
isValidChainQueryParam
} from '../types/ChainQueryParam'

export enum AmountQueryParamEnum {
MAX = 'max'
}
Expand All @@ -37,6 +42,8 @@ export const useArbQueryParams = () => {
]
*/
return useQueryParams({
from: ChainParam,
to: ChainParam,
chrstph-dvx marked this conversation as resolved.
Show resolved Hide resolved
amount: withDefault(AmountQueryParam, ''), // amount which is filled in Transfer panel
l2ChainId: NumberParam, // L2 chain-id with which we can initiaze (override) our networks/signer
token: StringParam, // import a new token using a Dialog Box
Expand Down Expand Up @@ -97,6 +104,23 @@ export const AmountQueryParam = {
}
}

export const ChainParam = {
fionnachan marked this conversation as resolved.
Show resolved Hide resolved
encode: (value: string | (string | null)[] | null | undefined) => value,
decode: (
value: string | (string | null)[] | null | undefined
): ChainQueryParam | undefined => {
if (typeof value !== 'string') {
return undefined
}

if (!isValidChainQueryParam(value)) {
return undefined
}

return value
}
}

export function ArbQueryParamProvider({
children
}: {
Expand Down
142 changes: 142 additions & 0 deletions packages/arb-token-bridge-ui/src/hooks/useNetworks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { Chain } from 'wagmi'
import { StaticJsonRpcProvider } from '@ethersproject/providers'
import { useCallback, useMemo } from 'react'

import { useArbQueryParams } from './useArbQueryParams'
import {
ChainQueryParam,
getChainForChainQueryParam,
getChainQueryParamForChain
} from '../types/ChainQueryParam'
import { ChainId, rpcURLs } from '../util/networks'
import { getPartnerChainsForChain } from '../util/wagmi/getPartnerChainsForChain'

function getPartnerChainsQueryParams(
chainQueryParam: ChainQueryParam
): ChainQueryParam[] {
const chain = getChainForChainQueryParam(chainQueryParam)
const partnerChains = getPartnerChainsForChain(chain)

return partnerChains.map(chain => getChainQueryParamForChain(chain.id))
}

const getProviderForChainCache: {
[rpcUrl: string]: StaticJsonRpcProvider
} = {
// start with empty cache
}

function createProviderWithCache(rpcUrl: string, chainId: ChainId) {
const provider = new StaticJsonRpcProvider(rpcUrl, chainId)
getProviderForChainCache[rpcUrl] = provider
return provider
}

function getProviderForChain(chain: Chain): StaticJsonRpcProvider {
const rpcUrl = rpcURLs[chain.id]

if (typeof rpcUrl === 'undefined') {
throw new Error(`[getProviderForChain] Unexpected chain id: ${chain.id}`)
}

const cachedProvider = getProviderForChainCache[rpcUrl]

if (typeof cachedProvider !== 'undefined') {
return cachedProvider
}

return createProviderWithCache(rpcUrl, chain.id)
}
chrstph-dvx marked this conversation as resolved.
Show resolved Hide resolved

export function sanitizeQueryParams({
from,
to
}: {
from: ChainQueryParam | undefined
to: ChainQueryParam | undefined
}): {
from: ChainQueryParam
to: ChainQueryParam
} {
// when both `from` and `to` are undefined, default to Ethereum and Arbitrum One
if (typeof from === 'undefined' && typeof to === 'undefined') {
return { from: 'ethereum', to: 'arbitrumOne' }
}

// only `from` is undefined
if (typeof from === 'undefined' && typeof to !== 'undefined') {
// get the counter
const [defaultFrom] = getPartnerChainsQueryParams(to)
return { from: defaultFrom!, to }
}

// only `to` is undefined
if (typeof from !== 'undefined' && typeof to === 'undefined') {
const [defaultTo] = getPartnerChainsQueryParams(from)
return { from, to: defaultTo as ChainQueryParam }
}

// both values are defined, but `to` is an invalid partner chain
if (!getPartnerChainsQueryParams(from!).includes(to!)) {
const [defaultTo] = getPartnerChainsQueryParams(from!)
return { from: from!, to: defaultTo! }
}

return { from: from!, to: to! }
}

export type UseNetworksState = {
from: Chain
fromProvider: StaticJsonRpcProvider
to: Chain
toProvider: StaticJsonRpcProvider
chrstph-dvx marked this conversation as resolved.
Show resolved Hide resolved
}

export type UseNetworksSetStateParams = { fromId: ChainId; toId?: ChainId }
export type UseNetworksSetState = (params: UseNetworksSetStateParams) => void

export function useNetworks(): [UseNetworksState, UseNetworksSetState] {
const [{ from, to }, setQueryParams] = useArbQueryParams()
const { from: validFrom, to: validTo } = sanitizeQueryParams({ from, to })

const setState = useCallback(
(params: UseNetworksSetStateParams) => {
const fromQueryParam = getChainQueryParamForChain(params.fromId)
if (!params.toId) {
const [toQueryParam] = getPartnerChainsQueryParams(fromQueryParam)
setQueryParams(
sanitizeQueryParams({ from: fromQueryParam, to: toQueryParam })
)
return
}

const toQueryParam = getChainQueryParamForChain(params.toId)

setQueryParams(
sanitizeQueryParams({ from: fromQueryParam, to: toQueryParam })
)
},
[setQueryParams]
)

if (from !== validFrom || to !== validTo) {
// On the first render, update query params with the sanitized values
setQueryParams({ from: validFrom, to: validTo })
}

// The return values of the hook will always be the sanitized values
return useMemo(() => {
const fromChain = getChainForChainQueryParam(validFrom)
const toChain = getChainForChainQueryParam(validTo)

return [
{
from: fromChain,
fromProvider: getProviderForChain(fromChain),
to: toChain,
toProvider: getProviderForChain(toChain)
},
setState
]
}, [validFrom, validTo, setState])
}
46 changes: 46 additions & 0 deletions packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts
spsjvc marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { StaticJsonRpcProvider } from '@ethersproject/providers'
import { useMemo } from 'react'
import { Chain } from 'wagmi'
import { isNetwork } from '../util/networks'
import { UseNetworksState } from './useNetworks'

type UseNetworksRelationshipState = {
childProvider: StaticJsonRpcProvider
childChain: Chain
parentChain: Chain
parentProvider: StaticJsonRpcProvider
chrstph-dvx marked this conversation as resolved.
Show resolved Hide resolved
}
export function useNetworksRelationship({
fromProvider,
from,
toProvider,
to
}: UseNetworksState): UseNetworksRelationshipState {
const fromNetwork = fromProvider.network
const toNetwork = toProvider.network
const {
isEthereumMainnet: isFromNetworkEthereum,
isArbitrum: isFromNetworkArbitrum
} = isNetwork(fromNetwork.chainId)
const { isOrbitChain: isToNetworkOrbitChain } = isNetwork(toNetwork.chainId)
const isFromNetworkParent =
isFromNetworkEthereum || (isFromNetworkArbitrum && isToNetworkOrbitChain)

return useMemo(() => {
spsjvc marked this conversation as resolved.
Show resolved Hide resolved
if (isFromNetworkParent) {
return {
parentChain: from,
parentProvider: fromProvider,
childProvider: toProvider,
childChain: to
}
}

return {
parentChain: to,
parentProvider: toProvider,
childProvider: fromProvider,
childChain: from
}
}, [fromProvider, from, toProvider, to, isFromNetworkParent])
}
92 changes: 92 additions & 0 deletions packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Chain } from 'wagmi'
import * as chains from 'wagmi/chains'

import { ChainId } from '../util/networks'
import * as customChains from '../util/wagmi/wagmiAdditionalNetworks'

const chainQueryParams = [
'ethereum',
'goerli',
'sepolia',
'arbitrumOne',
'arbitrumNova',
'arbitrumGoerli',
'arbitrumSepolia',
'stylus-testnet',
'xai-testnet'
] as const

export type ChainQueryParam = (typeof chainQueryParams)[number]
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should call this ChainKeyQueryParam? Meaning that ethereum, arbitrum-one etc. are of type ChainKeyQueryParam but type ChainQueryParam = ChainKeyQueryParam | number if we pass in id directly


export function isValidChainQueryParam(
value: string
): value is ChainQueryParam {
return (chainQueryParams as readonly string[]).includes(value)
}

export function getChainQueryParamForChain(chainId: ChainId): ChainQueryParam {
chrstph-dvx marked this conversation as resolved.
Show resolved Hide resolved
switch (chainId) {
case ChainId.Ethereum:
return 'ethereum'

case ChainId.Goerli:
return 'goerli'

case ChainId.ArbitrumOne:
return 'arbitrumOne'

case ChainId.ArbitrumNova:
return 'arbitrumNova'

case ChainId.ArbitrumGoerli:
return 'arbitrumGoerli'

case ChainId.StylusTestnet:
return 'stylus-testnet'

case ChainId.XaiTestnet:
return 'xai-testnet'

case ChainId.Sepolia:
return 'sepolia'

case ChainId.ArbitrumSepolia:
return 'arbitrumSepolia'

default:
throw new Error(
`[getChainQueryParamForChain] Unexpected chain id: ${chainId}`
)
chrstph-dvx marked this conversation as resolved.
Show resolved Hide resolved
}
}

export function getChainForChainQueryParam(value: ChainQueryParam): Chain {
switch (value) {
case 'ethereum':
return chains.mainnet

case 'goerli':
return chains.goerli

case 'sepolia':
return chains.sepolia

case 'arbitrumOne':
return chains.arbitrum

case 'arbitrumNova':
return customChains.arbitrumNova

case 'arbitrumGoerli':
return chains.arbitrumGoerli

case 'arbitrumSepolia':
return customChains.arbitrumSepolia

case 'stylus-testnet':
return customChains.stylusTestnet

case 'xai-testnet':
return customChains.xaiTestnet
}
}
Loading
Loading