Skip to content

Commit

Permalink
feat: autodetect LayerZero tokens (#1909)
Browse files Browse the repository at this point in the history
  • Loading branch information
fionnachan authored Sep 19, 2024
1 parent bf46d79 commit 5d67bf4
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { ERC20BridgeToken } from '../../hooks/arbTokenBridge.types'
import { warningToast } from '../common/atoms/Toast'
import { useNetworks } from '../../hooks/useNetworks'
import { useNetworksRelationship } from '../../hooks/useNetworksRelationship'
import { isWithdrawOnlyToken } from '../../util/WithdrawOnlyUtils'
import { isTransferDisabledToken } from '../../util/TokenTransferDisabledUtils'
import { useTransferDisabledDialogStore } from './TransferDisabledDialog'
import { TokenInfo } from './TokenInfo'
Expand Down Expand Up @@ -300,12 +299,6 @@ export function TokenImportDialog({
})
}

// do not allow import of withdraw-only tokens at deposit mode
if (isDepositMode && isWithdrawOnlyToken(l1Address, childChain.id)) {
openTransferDisabledDialog()
return
}

if (isTransferDisabledToken(l1Address, childChain.id)) {
openTransferDisabledDialog()
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import { TokenRow } from './TokenRow'
import { useNetworks } from '../../hooks/useNetworks'
import { useNetworksRelationship } from '../../hooks/useNetworksRelationship'
import { useTransferDisabledDialogStore } from './TransferDisabledDialog'
import { isWithdrawOnlyToken } from '../../util/WithdrawOnlyUtils'
import { isTransferDisabledToken } from '../../util/TokenTransferDisabledUtils'
import { useTokenFromSearchParams } from './TransferPanelUtils'
import { Switch } from '../common/atoms/Switch'
Expand Down Expand Up @@ -371,6 +370,8 @@ function TokensPanel({
isArbitrumOne,
isArbitrumSepolia,
isOrbitChain,
isParentChainArbitrumOne,
isParentChainArbitrumSepolia,
getBalance,
nativeCurrency
])
Expand Down Expand Up @@ -535,7 +536,6 @@ export function TokenSearch({
childChainProvider,
parentChain,
parentChainProvider,
isDepositMode,
isTeleportMode
} = useNetworksRelationship(networks)
const { updateUSDCBalances } = useUpdateUSDCBalances({ walletAddress })
Expand Down Expand Up @@ -631,12 +631,6 @@ export function TokenSearch({
})
}

// do not allow import of withdraw-only tokens at deposit mode
if (isDepositMode && isWithdrawOnlyToken(_token.address, childChain.id)) {
openTransferDisabledDialog()
return
}

if (isTransferDisabledToken(_token.address, childChain.id)) {
openTransferDisabledDialog()
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ChainId, getNetworkName } from '../../util/networks'
import { getL2ConfigForTeleport } from '../../token-bridge-sdk/teleport'
import { useNetworksRelationship } from '../../hooks/useNetworksRelationship'
import { withdrawOnlyTokens } from '../../util/WithdrawOnlyUtils'
import { useSelectedTokenIsWithdrawOnly } from './hooks/useSelectedTokenIsWithdrawOnly'

type TransferDisabledDialogStore = {
isOpen: boolean
Expand All @@ -26,14 +27,17 @@ export const useTransferDisabledDialogStore =

export function TransferDisabledDialog() {
const [networks] = useNetworks()
const { isTeleportMode } = useNetworksRelationship(networks)
const { isDepositMode, isTeleportMode } = useNetworksRelationship(networks)
const { app } = useAppState()
const { selectedToken } = app
const {
app: { setSelectedToken }
} = useActions()
const { isSelectedTokenWithdrawOnly, isSelectedTokenWithdrawOnlyLoading } =
useSelectedTokenIsWithdrawOnly()
const {
isOpen: isOpenTransferDisabledDialog,
openDialog: openTransferDisabledDialog,
closeDialog: closeTransferDisabledDialog
} = useTransferDisabledDialogStore()
const unsupportedToken = sanitizeTokenSymbol(selectedToken?.symbol ?? '', {
Expand All @@ -57,6 +61,22 @@ export function TransferDisabledDialog() {
updateL2ChainIdForTeleport()
}, [isTeleportMode, networks.destinationChainProvider])

useEffect(() => {
// do not allow import of withdraw-only tokens at deposit mode
if (
isDepositMode &&
isSelectedTokenWithdrawOnly &&
!isSelectedTokenWithdrawOnlyLoading
) {
openTransferDisabledDialog()
}
}, [
isSelectedTokenWithdrawOnly,
isDepositMode,
openTransferDisabledDialog,
isSelectedTokenWithdrawOnlyLoading
])

const onClose = () => {
setSelectedToken(null)
closeTransferDisabledDialog()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,17 +185,16 @@ function ErrorMessage({
case TransferReadinessRichErrorMessage.TOKEN_WITHDRAW_ONLY:
case TransferReadinessRichErrorMessage.TOKEN_TRANSFER_DISABLED:
return (
<>
<span className="text-sm text-brick">
This token can&apos;t be bridged over.
</span>{' '}
<div className="text-sm text-brick">
<span>This token can&apos;t be bridged over.</span>{' '}
<button
className="arb-hover underline"
onClick={openTransferDisabledDialog}
>
Learn more.
Learn more
</button>
</>
<span>.</span>
</div>
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import useSWRImmutable from 'swr/immutable'
import { useMemo } from 'react'

import { useAppState } from '../../../state'
import { useNetworks } from '../../../hooks/useNetworks'
import { useNetworksRelationship } from '../../../hooks/useNetworksRelationship'
import { isWithdrawOnlyToken } from '../../../util/WithdrawOnlyUtils'

export function useSelectedTokenIsWithdrawOnly() {
const {
app: { selectedToken }
} = useAppState()
const [networks] = useNetworks()
const { isDepositMode, parentChain, childChain } =
useNetworksRelationship(networks)

const queryKey = useMemo(() => {
if (!selectedToken) {
return null
}
if (!isDepositMode) {
return null
}
return [
selectedToken.address.toLowerCase(),
parentChain.id,
childChain.id
] as const
}, [selectedToken, isDepositMode, parentChain.id, childChain.id])

const { data: isSelectedTokenWithdrawOnly, isLoading } = useSWRImmutable(
queryKey,
([parentChainErc20Address, parentChainId, childChainId]) =>
isWithdrawOnlyToken({
parentChainErc20Address,
parentChainId,
childChainId
})
)

return {
isSelectedTokenWithdrawOnly,
isSelectedTokenWithdrawOnlyLoading: isLoading
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
} from '../../util/TokenUtils'
import { useAppContextState } from '../App/AppContext'
import { useDestinationAddressStore } from './AdvancedSettings'
import { isWithdrawOnlyToken } from '../../util/WithdrawOnlyUtils'
import {
TransferReadinessRichErrorMessage,
getInsufficientFundsErrorMessage,
Expand All @@ -32,6 +31,7 @@ import { isNetwork } from '../../util/networks'
import { useBalances } from '../../hooks/useBalances'
import { useArbQueryParams } from '../../hooks/useArbQueryParams'
import { formatAmount } from '../../util/NumberUtils'
import { useSelectedTokenIsWithdrawOnly } from './hooks/useSelectedTokenIsWithdrawOnly'

// Add chains IDs that are currently down or disabled
// It will block transfers and display an info box in the transfer panel
Expand Down Expand Up @@ -136,6 +136,8 @@ export function useTransferReadiness(): UseTransferReadinessResult {
isTeleportMode
} = useNetworksRelationship(networks)

const { isSelectedTokenWithdrawOnly, isSelectedTokenWithdrawOnlyLoading } =
useSelectedTokenIsWithdrawOnly()
const gasSummary = useGasSummary()
const { address: walletAddress } = useAccount()
const { isSmartContractWallet } = useAccountType()
Expand Down Expand Up @@ -293,11 +295,6 @@ export function useTransferReadiness(): UseTransferReadinessResult {

// ERC-20
if (selectedToken) {
const selectedTokenIsWithdrawOnly = isWithdrawOnlyToken(
selectedToken.address,
childChain.id
)

const selectedTokenIsDisabled =
isTransferDisabledToken(selectedToken.address, childChain.id) ||
(isTeleportMode &&
Expand All @@ -307,7 +304,11 @@ export function useTransferReadiness(): UseTransferReadinessResult {
childChain.id
))

if (isDepositMode && selectedTokenIsWithdrawOnly) {
if (
isDepositMode &&
isSelectedTokenWithdrawOnly &&
!isSelectedTokenWithdrawOnlyLoading
) {
return notReady({
errorMessages: {
inputAmount1: TransferReadinessRichErrorMessage.TOKEN_WITHDRAW_ONLY
Expand Down
62 changes: 52 additions & 10 deletions packages/arb-token-bridge-ui/src/util/WithdrawOnlyUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// tokens that can't be bridged to Arbitrum (maybe coz they have their native protocol bridges and custom implementation or they are being discontinued)
// the UI doesn't let users deposit such tokens. If bridged already, these can only be withdrawn.

import { ethers } from 'ethers'
import { getProviderForChainId } from '@/token-bridge-sdk/utils'

import { ChainId, isNetwork } from '../util/networks'
import {
isTokenArbitrumOneUSDCe,
Expand Down Expand Up @@ -130,12 +133,6 @@ export const withdrawOnlyTokens: { [chainId: number]: WithdrawOnlyToken[] } = {
l1Address: '0x137dDB47Ee24EaA998a535Ab00378d6BFa84F893',
l2Address: '0xa4431f62db9955bfd056c30e5ae703bf0d0eaec8'
},
{
symbol: 'GSWIFT',
l2CustomAddr: '0x580e933d90091b9ce380740e3a4a39c67eb85b4c',
l1Address: '0x580e933d90091b9ce380740e3a4a39c67eb85b4c',
l2Address: '0x88e5369f73312eba739dcdf83bdb8bad3d08f4c8'
},
{
symbol: 'eETH',
l2CustomAddr: '',
Expand Down Expand Up @@ -196,6 +193,13 @@ export const withdrawOnlyTokens: { [chainId: number]: WithdrawOnlyToken[] } = {
l1Address: '0x0B7f0e51Cd1739D6C96982D55aD8fA634dd43A9C',
l2Address: '0x6Ab317237cc72B2cdb54EcfcC180b61E00F7df76'
},
// LayerZero tokens
{
symbol: 'GSWIFT',
l2CustomAddr: '0x580e933d90091b9ce380740e3a4a39c67eb85b4c',
l1Address: '0x580e933d90091b9ce380740e3a4a39c67eb85b4c',
l2Address: '0x88e5369f73312eba739dcdf83bdb8bad3d08f4c8'
},
{
symbol: 'ZRO',
l2CustomAddr: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
Expand All @@ -206,15 +210,43 @@ export const withdrawOnlyTokens: { [chainId: number]: WithdrawOnlyToken[] } = {
[ChainId.ArbitrumNova]: []
}

async function isLayerZeroToken(
parentChainErc20Address: string,
parentChainId: number
) {
const parentProvider = getProviderForChainId(parentChainId)

// https://github.com/LayerZero-Labs/LayerZero-v2/blob/592625b9e5967643853476445ffe0e777360b906/packages/layerzero-v2/evm/oapp/contracts/oft/OFT.sol#L37
const layerZeroTokenOftContract = new ethers.Contract(
parentChainErc20Address,
[
'function oftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version)'
],
parentProvider
)

try {
const _isLayerZeroToken = await layerZeroTokenOftContract.oftVersion()
return !!_isLayerZeroToken
} catch (error) {
return false
}
}

/**
*
* @param erc20L1Address
* @param childChainId
*/
export function isWithdrawOnlyToken(
parentChainErc20Address: string,
export async function isWithdrawOnlyToken({
parentChainErc20Address,
parentChainId,
childChainId
}: {
parentChainErc20Address: string
parentChainId: number
childChainId: number
) {
}) {
// disable USDC.e deposits for Orbit chains
if (
(isTokenArbitrumOneUSDCe(parentChainErc20Address) ||
Expand All @@ -224,7 +256,17 @@ export function isWithdrawOnlyToken(
return true
}

return (withdrawOnlyTokens[childChainId] ?? [])
const inWithdrawOnlyList = (withdrawOnlyTokens[childChainId] ?? [])
.map(token => token.l1Address.toLowerCase())
.includes(parentChainErc20Address.toLowerCase())

if (inWithdrawOnlyList) {
return true
}

if (await isLayerZeroToken(parentChainErc20Address, parentChainId)) {
return true
}

return false
}

0 comments on commit 5d67bf4

Please sign in to comment.