Skip to content

refactor: use token list gateways if possible #2172

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -209,21 +209,23 @@ export function TokenApprovalDialog(props: TokenApprovalDialogProps) {
}

if (isDepositMode) {
setContractAddress(
await fetchErc20ParentChainGatewayAddress({
const parentGatewayAddress =
token.parentBridgeAddress ??
(await fetchErc20ParentChainGatewayAddress({
erc20ParentChainAddress: token.address,
parentChainProvider,
childChainProvider
})
)
}))
setContractAddress(parentGatewayAddress)
return
}
setContractAddress(
await fetchErc20L2GatewayAddress({
const childGatewayAddress =
token.childBridgeAddress ??
(await fetchErc20L2GatewayAddress({
erc20L1Address: token.address,
l2Provider: childChainProvider
})
)
}))
setContractAddress(childGatewayAddress)
}
getContractAddress()
}, [
Expand All @@ -234,6 +236,8 @@ export function TokenApprovalDialog(props: TokenApprovalDialogProps) {
parentChainProvider,
token?.address,
token?.l2Address,
token?.parentBridgeAddress,
token?.childBridgeAddress,
sourceChain.id,
destinationChain.id,
isTeleportMode,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { getProviderForChainId } from '@/token-bridge-sdk/utils'
import { ChainId } from '../../util/networks'
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to be updated since the reference has changed. Otherwise test fails.

import { processTokensFromList } from '../useArbTokenBridge'
import {
BRIDGE_TOKEN_LISTS,
fetchTokenListFromURL
} from '../../util/TokenListUtils'
import {
fetchErc20L2GatewayAddress,
fetchErc20ParentChainGatewayAddress
} from '../../util/TokenUtils'

// can be found on https://tokenlist.arbitrum.io/ArbTokenLists/arbed_coinmarketcap.json
// standard gateway token
const wBtcAddressOnEthereum =
'0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'.toLowerCase()

// custom gateway token
const lptAddressOnEthereum =
'0x58b6a8a3302369daec383334672404ee733ab239'.toLowerCase()

const parentProvider = getProviderForChainId(ChainId.Ethereum)

Check failure on line 22 in packages/arb-token-bridge-ui/src/hooks/__tests__/useArbTokenBridge.test.ts

View workflow job for this annotation

GitHub Actions / Test UI

src/hooks/__tests__/useArbTokenBridge.test.ts

TypeError: Cannot read properties of undefined (reading 'Ethereum') ❯ src/hooks/__tests__/useArbTokenBridge.test.ts:22:54
const childProvider = getProviderForChainId(ChainId.ArbitrumOne)

describe('processTokensFromList', () => {
it('token parent bridge address should be same as fetched parent gateway address', async () => {
const { data: bridgeTokenList } = await fetchTokenListFromURL(
BRIDGE_TOKEN_LISTS[2]!.url
)

const { bridgeTokensToAdd } = await processTokensFromList({
arbTokenList: bridgeTokenList!,
listId: BRIDGE_TOKEN_LISTS[2]!.id,
parentChainId: ChainId.Ethereum,
childChainId: ChainId.ArbitrumOne
})

// WBTC, standard gateway
const fetchedWbtcParentGatewayAddress =
await fetchErc20ParentChainGatewayAddress({
erc20ParentChainAddress: wBtcAddressOnEthereum,
parentChainProvider: parentProvider,
childChainProvider: childProvider
})

expect(
bridgeTokensToAdd[
wBtcAddressOnEthereum
]?.parentBridgeAddress?.toLowerCase()
).toEqual(fetchedWbtcParentGatewayAddress.toLowerCase())

// LPT, custom gateway
const fetchedLptParentGatewayAddress =
await fetchErc20ParentChainGatewayAddress({
erc20ParentChainAddress: lptAddressOnEthereum,
parentChainProvider: parentProvider,
childChainProvider: childProvider
})

expect(
bridgeTokensToAdd[
lptAddressOnEthereum
]?.parentBridgeAddress?.toLowerCase()
).toEqual(fetchedLptParentGatewayAddress.toLowerCase())
})

it('token child bridge address should be same as fetched child gateway address', async () => {
const { data: bridgeTokenList } = await fetchTokenListFromURL(
BRIDGE_TOKEN_LISTS[2]!.url
)

const { bridgeTokensToAdd } = await processTokensFromList({
arbTokenList: bridgeTokenList!,
listId: BRIDGE_TOKEN_LISTS[2]!.id,
parentChainId: ChainId.Ethereum,
childChainId: ChainId.ArbitrumOne
})

// WBTC, standard gateway
const fetchedWbtcChildGatewayAddress = await fetchErc20L2GatewayAddress({
erc20L1Address: wBtcAddressOnEthereum,
l2Provider: childProvider
})

expect(
bridgeTokensToAdd[
wBtcAddressOnEthereum
]?.childBridgeAddress?.toLowerCase()
).toEqual(fetchedWbtcChildGatewayAddress.toLowerCase())

// LPT, custom gateway
const fetchedLptChildGatewayAddress = await fetchErc20L2GatewayAddress({
erc20L1Address: lptAddressOnEthereum,
l2Provider: childProvider
})

expect(
bridgeTokensToAdd[lptAddressOnEthereum]?.childBridgeAddress?.toLowerCase()
).toEqual(fetchedLptChildGatewayAddress.toLowerCase())
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export interface BridgeToken {
export interface ERC20BridgeToken extends BridgeToken {
type: TokenType.ERC20
decimals: number
parentBridgeAddress?: string
childBridgeAddress?: string
}

export interface ContractStorage<T> {
Expand Down
178 changes: 100 additions & 78 deletions packages/arb-token-bridge-ui/src/hooks/useArbTokenBridge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useState, useMemo } from 'react'
import { useCallback, useState } from 'react'
import { Chain, useAccount } from 'wagmi'
import { BigNumber } from 'ethers'
import { Signer } from '@ethersproject/abstract-signer'
Expand Down Expand Up @@ -79,6 +79,98 @@ class TokenDisabledError extends Error {
}
}

export async function processTokensFromList({
arbTokenList,
listId,
parentChainId,
childChainId
}: {
arbTokenList: TokenList
listId: string
parentChainId: number
childChainId: number
}) {
const bridgeTokensToAdd: ContractStorage<ERC20BridgeToken> = {}
const candidateUnbridgedTokensToAdd: ERC20BridgeToken[] = []

for (const tokenData of arbTokenList.tokens) {
const { address, name, symbol, extensions, decimals, logoURI, chainId } =
tokenData

if (![parentChainId, childChainId].includes(chainId)) {
continue
}

const bridgeInfo = (() => {
// TODO: parsing the token list format could be from arbts or the tokenlist package
interface Extensions {
bridgeInfo: {
[chainId: string]: {
tokenAddress: string
originBridgeAddress: string
destBridgeAddress: string
}
}
}
const isExtensions = (obj: any): obj is Extensions => {
if (!obj) return false
if (!obj['bridgeInfo']) return false
return Object.keys(obj['bridgeInfo'])
.map(key => obj['bridgeInfo'][key])
.every(
e =>
e &&
'tokenAddress' in e &&
'originBridgeAddress' in e &&
'destBridgeAddress' in e
)
}
if (!isExtensions(extensions)) {
return null
} else {
return extensions.bridgeInfo
}
})()

if (bridgeInfo) {
const l1Address = bridgeInfo[parentChainId]?.tokenAddress.toLowerCase()

if (l1Address) {
bridgeTokensToAdd[l1Address] = {
name,
type: TokenType.ERC20,
symbol,
address: l1Address,
l2Address: address.toLowerCase(),
parentBridgeAddress: bridgeInfo[parentChainId]?.destBridgeAddress,
childBridgeAddress: bridgeInfo[parentChainId]?.originBridgeAddress,
decimals,
logoURI,
listIds: new Set([listId])
}
}
}
// save potentially unbridged L1 tokens:
// stopgap: giant lists (i.e., CMC list) currently severaly hurts page performace, so for now we only add the bridged tokens
else if (arbTokenList.tokens.length < 1000) {
candidateUnbridgedTokensToAdd.push({
name,
type: TokenType.ERC20,
symbol,
address: address.toLowerCase(),
decimals,
logoURI,
listIds: new Set([listId])
})
}
}

return {
bridgeTokensToAdd,
candidateUnbridgedTokensToAdd
}
}

export interface TokenBridgeParams {
l1: { provider: JsonRpcProvider; network: Chain }
l2: { provider: JsonRpcProvider; network: Chain }
Expand Down Expand Up @@ -134,8 +226,6 @@ export const useArbTokenBridge = (
React.Dispatch<void>
]

const l1NetworkID = useMemo(() => String(l1.network.id), [l1.network.id])

const removeTokensFromList = (listID: string) => {
setBridgeTokens(prevBridgeTokens => {
const newBridgeTokens = { ...prevBridgeTokens }
Expand All @@ -157,81 +247,13 @@ export const useArbTokenBridge = (
const l1ChainID = l1.network.id
const l2ChainID = l2.network.id

const bridgeTokensToAdd: ContractStorage<ERC20BridgeToken> = {}

const candidateUnbridgedTokensToAdd: ERC20BridgeToken[] = []

for (const tokenData of arbTokenList.tokens) {
const { address, name, symbol, extensions, decimals, logoURI, chainId } =
tokenData

if (![l1ChainID, l2ChainID].includes(chainId)) {
continue
}

const bridgeInfo = (() => {
// TODO: parsing the token list format could be from arbts or the tokenlist package
interface Extensions {
bridgeInfo: {
[chainId: string]: {
tokenAddress: string
originBridgeAddress: string
destBridgeAddress: string
}
}
}
const isExtensions = (obj: any): obj is Extensions => {
if (!obj) return false
if (!obj['bridgeInfo']) return false
return Object.keys(obj['bridgeInfo'])
.map(key => obj['bridgeInfo'][key])
.every(
e =>
e &&
'tokenAddress' in e &&
'originBridgeAddress' in e &&
'destBridgeAddress' in e
)
}
if (!isExtensions(extensions)) {
return null
} else {
return extensions.bridgeInfo
}
})()

if (bridgeInfo) {
const l1Address = bridgeInfo[l1NetworkID]?.tokenAddress.toLowerCase()

if (!l1Address) {
return
}

bridgeTokensToAdd[l1Address] = {
name,
type: TokenType.ERC20,
symbol,
address: l1Address,
l2Address: address.toLowerCase(),
decimals,
logoURI,
listIds: new Set([listId])
}
}
// save potentially unbridged L1 tokens:
// stopgap: giant lists (i.e., CMC list) currently severaly hurts page performace, so for now we only add the bridged tokens
else if (arbTokenList.tokens.length < 1000) {
candidateUnbridgedTokensToAdd.push({
name,
type: TokenType.ERC20,
symbol,
address: address.toLowerCase(),
decimals,
logoURI,
listIds: new Set([listId])
})
}
}
const { bridgeTokensToAdd, candidateUnbridgedTokensToAdd } =
await processTokensFromList({
arbTokenList,
listId,
parentChainId: l1ChainID,
childChainId: l2ChainID
})

Comment on lines +250 to +256
Copy link
Member Author

@fionnachan fionnachan Jan 8, 2025

Choose a reason for hiding this comment

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

had to move the processing code out, otherwise we will have to mock all the external and internal hooks, e.g. useAccount from wagmi, useQueryParams etc in order to render useArbTokenBridge in the test file, which points to an issue of this hook and make it pretty much untestable

// add L1 tokens only if they aren't already bridged (i.e., if they haven't already beed added as L2 arb-tokens to the list)
const l1AddressesOfBridgedTokens = new Set(
Expand Down
Loading