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 new routes components #2279

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
18 changes: 18 additions & 0 deletions packages/arb-token-bridge-ui/public/icons/arbitrum.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/arb-token-bridge-ui/public/icons/bridge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/arb-token-bridge-ui/public/icons/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions packages/arb-token-bridge-ui/public/icons/duration.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions packages/arb-token-bridge-ui/public/icons/gas.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/arb-token-bridge-ui/public/icons/layerzero.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useTransferReadiness } from './useTransferReadiness'
import { useAccountType } from '../../hooks/useAccountType'
import { useNetworksRelationship } from '../../hooks/useNetworksRelationship'
import { getNetworkName } from '../../util/networks'
import { useRouteStore } from './hooks/useRouteStore'

export function MoveFundsButton({
onClick
Expand All @@ -22,14 +23,16 @@ export function MoveFundsButton({
)
const { isSmartContractWallet } = useAccountType()
const { transferReady } = useTransferReadiness()
const { selectedRoute } = useRouteStore()
const isDisabled =
selectedRoute === undefined ||
(isDepositMode ? !transferReady.deposit : !transferReady.withdrawal)

return (
<Button
variant="primary"
loading={isTransferring}
disabled={
isDepositMode ? !transferReady.deposit : !transferReady.withdrawal
}
disabled={isDisabled}
onClick={onClick}
style={{
borderColor: destinationChainUIcolor,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
import { useNetworks } from '../../../hooks/useNetworks'
import { useNetworksRelationship } from '../../../hooks/useNetworksRelationship'
import { constants, utils } from 'ethers'
import { Route, Token } from './Route'
import { useAmountBigNumber } from '../hooks/useAmountBigNumber'
import {
UseGasSummaryResult,
useGasSummary
} from '../../../hooks/TransferPanel/useGasSummary'
import {
NativeCurrency,
useNativeCurrency
} from '../../../hooks/useNativeCurrency'
import { CommonAddress } from '../../../util/CommonAddressUtils'
import {
getOrbitDepositDuration,
getStandardDepositDuration,
getWithdrawalDuration
} from '../../../hooks/useTransferDuration'
import { isNetwork } from '../../../util/networks'
import dayjs from 'dayjs'
import { useSelectedToken } from '../../../hooks/useSelectedToken'
import { isTokenNativeUSDC } from '../../../util/TokenUtils'
import { useRouteStore } from '../hooks/useRouteStore'
import { useMemo } from 'react'
import { ERC20BridgeToken } from '../../../hooks/arbTokenBridge.types'

const commonUsdcToken = {
decimals: 6,
address: CommonAddress.Ethereum.USDC
}

const bridgedUsdcToken = {
...commonUsdcToken,
name: 'Bridged USDC',
symbol: 'USDC.e',
l2Address: CommonAddress.ArbitrumOne['USDC.e']
}

const nativeUsdcToken = {
...commonUsdcToken,
name: 'USDC',
symbol: 'USDC',
l2Address: CommonAddress.ArbitrumOne.USDC
}

function getDuration({
isTestnet,
sourceChainId,
isTeleportMode,
isWithdrawal,
isOrbitChain
}: {
isTestnet: boolean
sourceChainId: number
isTeleportMode: boolean
isWithdrawal: boolean
isOrbitChain: boolean
}) {
if (isTeleportMode) {
return (
getStandardDepositDuration(isTestnet) + getOrbitDepositDuration(isTestnet)
)
}

if (isWithdrawal) {
return getWithdrawalDuration({
createdAt: dayjs().valueOf(),
sourceChainId: sourceChainId
})
}

if (isOrbitChain) {
return getOrbitDepositDuration(isTestnet)
}

return getStandardDepositDuration(isTestnet)
}

function getGasCostAndToken({
childChainNativeCurrency,
parentChainNativeCurrency,
gasSummaryStatus,
estimatedChildChainGasFees,
estimatedParentChainGasFees,
isDepositMode,
selectedToken
}: {
childChainNativeCurrency: NativeCurrency
parentChainNativeCurrency: NativeCurrency
gasSummaryStatus: UseGasSummaryResult['status']
estimatedChildChainGasFees: UseGasSummaryResult['estimatedChildChainGasFees']
estimatedParentChainGasFees: UseGasSummaryResult['estimatedParentChainGasFees']
isDepositMode: boolean
selectedToken: ERC20BridgeToken | null
}): {
Comment on lines +80 to +96
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function is accepting lots of parameters rather than being a hook with lots of internal calls to hook to make it easier to test

isLoading: boolean
gasCost: { gasCost: number; gasToken: Token }[] | null
} {
const sameNativeCurrency =
childChainNativeCurrency.isCustom === parentChainNativeCurrency.isCustom
const estimatedTotalGasFees =
gasSummaryStatus === 'loading' ||
typeof estimatedChildChainGasFees == 'undefined' ||
typeof estimatedParentChainGasFees == 'undefined'
? undefined
: estimatedParentChainGasFees + estimatedChildChainGasFees

const childChainNativeCurrencyWithAddress: Token =
'address' in childChainNativeCurrency
? childChainNativeCurrency
: { ...childChainNativeCurrency, address: constants.AddressZero }

const parentChainNativeCurrencyWithAddress: Token =
'address' in parentChainNativeCurrency
? parentChainNativeCurrency
: { ...parentChainNativeCurrency, address: constants.AddressZero }

if (typeof estimatedTotalGasFees === 'undefined') {
return {
gasCost: null,
isLoading: true
}
}

/**
* Same Native Currencies between Parent and Child chains
* 1. ETH/ER20 deposit: L1->L2
* 2. ETH/ERC20 withdrawal: L2->L1
* 3. ETH/ER20 deposit: L2->L3 (ETH as gas token)
* 4. ETH/ERC20 withdrawal: L3 (ETH as gas token)->L2
*
* x ETH
*/
if (sameNativeCurrency) {
return {
isLoading: false,
gasCost: [
{
gasCost: estimatedTotalGasFees,
gasToken: childChainNativeCurrencyWithAddress
}
]
}
}

/** Different Native Currencies between Parent and Child chains
*
* Custom gas token deposit: L2->Xai
* x ETH
*
* ERC20 deposit: L2->Xai
* x ETH and x XAI
*
* Custom gas token/ERC20 withdrawal: L3->L2
* only show child chain native currency
* x XAI
*/
if (isDepositMode) {
const gasCost: { gasCost: number; gasToken: Token }[] = [
{
gasCost: estimatedParentChainGasFees!,
gasToken: parentChainNativeCurrencyWithAddress
}
]

if (selectedToken) {
gasCost.push({
gasCost: estimatedChildChainGasFees!,
gasToken: childChainNativeCurrencyWithAddress
})
}

return {
gasCost,
isLoading: false
}
}

return {
isLoading: false,
gasCost: [
{
gasCost: estimatedChildChainGasFees!,
gasToken: childChainNativeCurrencyWithAddress
}
]
}
}

export function ArbitrumRoute() {
const amount = useAmountBigNumber()
const [networks] = useNetworks()
const {
childChain,
isTeleportMode,
childChainProvider,
parentChainProvider,
isDepositMode
} = useNetworksRelationship(networks)
const {
status: gasSummaryStatus,
estimatedParentChainGasFees,
estimatedChildChainGasFees
} = useGasSummary()
const childChainNativeCurrency = useNativeCurrency({
provider: childChainProvider
})
const parentChainNativeCurrency = useNativeCurrency({
provider: parentChainProvider
})
const { isTestnet, isOrbitChain } = isNetwork(childChain.id)

const selectedRoute = useRouteStore(state => state.selectedRoute)
const [selectedToken] = useSelectedToken()

const { gasCost, isLoading } = useMemo(
() =>
getGasCostAndToken({
childChainNativeCurrency,
parentChainNativeCurrency,
gasSummaryStatus,
estimatedChildChainGasFees,
estimatedParentChainGasFees,
isDepositMode,
selectedToken
}),
[
childChainNativeCurrency,
estimatedChildChainGasFees,
estimatedParentChainGasFees,
gasSummaryStatus,
isDepositMode,
parentChainNativeCurrency,
selectedToken
]
)

/**
* For USDC:
* - Withdrawing USDC.e, we receive USDC on Mainnet
* - Depositing USDC, we receive USDC.e on Arbitrum
*/
const isUsdcTransfer = isTokenNativeUSDC(selectedToken?.address)
const overrideToken = isDepositMode ? bridgedUsdcToken : nativeUsdcToken
const durationMs =
getDuration({
isTestnet,
isWithdrawal: !isDepositMode,
sourceChainId: networks.sourceChain.id,
isTeleportMode,
isOrbitChain
}) *
60 *
1_000

return (
<Route
type="arbitrum"
bridge={'Arbitrum Bridge'}
bridgeIconURI={'/icons/arbitrum.svg'}
durationMs={durationMs}
amountReceived={amount.toString()}
isLoadingGasEstimate={isLoading}
overrideToken={isUsdcTransfer ? overrideToken : undefined}
gasCost={
gasCost && gasCost.length > 0
? gasCost.map(({ gasCost, gasToken }) => ({
gasCost: utils
.parseUnits(gasCost.toFixed(18), gasToken.decimals)
.toString(),
gasToken
}))
: []
}
tag={'security-guaranteed'}
selected={selectedRoute === 'arbitrum'}
/>
)
}
Loading
Loading