Skip to content

Commit

Permalink
feat: adds support for fast confirmation times (#1901)
Browse files Browse the repository at this point in the history
  • Loading branch information
douglance authored Nov 12, 2024
1 parent 19202a4 commit c427bdd
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useMemo } from 'react'
import { InformationCircleIcon } from '@heroicons/react/24/outline'
import { twMerge } from 'tailwind-merge'

import { formatAmount } from '../../util/NumberUtils'
Expand All @@ -13,10 +14,12 @@ import { useNetworksRelationship } from '../../hooks/useNetworksRelationship'
import { NativeCurrencyPrice, useIsBridgingEth } from './NativeCurrencyPrice'
import { useAppState } from '../../state'
import { Loader } from '../common/atoms/Loader'
import { Tooltip } from '../common/Tooltip'
import { isTokenNativeUSDC } from '../../util/TokenUtils'
import { NoteBox } from '../common/NoteBox'
import { DISABLED_CHAIN_IDS } from './useTransferReadiness'
import { useIsBatchTransferSupported } from '../../hooks/TransferPanel/useIsBatchTransferSupported'
import { getConfirmationTime } from '../../util/WithdrawalUtils'

export type TransferPanelSummaryToken = {
symbol: string
Expand Down Expand Up @@ -259,6 +262,47 @@ export function TransferPanelSummary({ token }: TransferPanelSummaryProps) {
)}
</span>
</div>
{!isDepositMode &&
(isDestinationChainArbitrumOne ||
isDestinationChainArbitrumSepolia) && (
<div
className={twMerge(
'grid grid-cols-[260px_auto] items-center text-sm font-light'
)}
>
<ConfirmationTimeInfo chainId={networks.sourceChain.id} />
</div>
)}
</TransferPanelSummaryContainer>
)
}

function ConfirmationTimeInfo({ chainId }: { chainId: number }) {
const {
confirmationTimeInReadableFormat,
confirmationTimeInReadableFormatShort,
isDefaultConfirmationTime
} = getConfirmationTime(chainId)
return (
<>
<span className="whitespace-nowrap">Confirmation time:</span>
<span className="flex items-center font-medium">
<span className="hidden sm:inline">
{confirmationTimeInReadableFormat}
</span>
<span className="sm:hidden">
{confirmationTimeInReadableFormatShort}
</span>
{!isDefaultConfirmationTime && (
<Tooltip
content={
'Fast Withdrawals relies on a committee of validators. In the event of a committee outage, your withdrawal falls back to the 7 day challenge period secured by Arbitrum Fraud Proofs.'
}
>
<InformationCircleIcon className="ml-1 h-3 w-3" />
</Tooltip>
)}
</span>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ import { useAppState } from '../../state'
import { trackEvent } from '../../util/AnalyticsUtils'
import { getNetworkName, isNetwork } from '../../util/networks'
import { getFastBridges } from '../../util/fastBridges'
import { CONFIRMATION_PERIOD_ARTICLE_LINK } from '../../constants'
import {
CONFIRMATION_PERIOD_ARTICLE_LINK,
FAST_WITHDRAWAL_DOCS_ARTICLE_LINK
} from '../../constants'
import { useNativeCurrency } from '../../hooks/useNativeCurrency'
import { useNetworks } from '../../hooks/useNetworks'
import { useNetworksRelationship } from '../../hooks/useNetworksRelationship'
import { SecurityGuaranteed, SecurityNotGuaranteed } from './SecurityLabels'
import { getWithdrawalConfirmationDate } from '../../hooks/useTransferDuration'
import { getConfirmationTime } from '../../util/WithdrawalUtils'

function getCalendarUrl(
withdrawalDate: dayjs.Dayjs,
Expand All @@ -42,6 +46,8 @@ export function WithdrawalConfirmationDialog(
const { childChain, childChainProvider, parentChain } =
useNetworksRelationship(networks)

const { fastWithdrawalActive } = getConfirmationTime(childChain.id)

const [selectedIndex, setSelectedIndex] = useState(0)

const destinationNetworkName = getNetworkName(parentChain.id)
Expand All @@ -63,9 +69,14 @@ export function WithdrawalConfirmationDialog(

const [checkbox1Checked, setCheckbox1Checked] = useState(false)
const [checkbox2Checked, setCheckbox2Checked] = useState(false)
const [checkbox3Checked, setCheckbox3Checked] = useState(false)

const { isArbitrumOne } = isNetwork(childChain.id)
const bothCheckboxesChecked = checkbox1Checked && checkbox2Checked

const allCheckboxesChecked =
checkbox1Checked &&
checkbox2Checked &&
(fastWithdrawalActive ? checkbox3Checked : true)

const estimatedConfirmationDate = getWithdrawalConfirmationDate({
createdAt: null,
Expand All @@ -81,6 +92,7 @@ export function WithdrawalConfirmationDialog(

setCheckbox1Checked(false)
setCheckbox2Checked(false)
setCheckbox3Checked(false)
setSelectedIndex(0)
}

Expand All @@ -91,7 +103,7 @@ export function WithdrawalConfirmationDialog(
className="max-w-[700px]"
title={`Move funds to ${destinationNetworkName}`}
actionButtonProps={{
disabled: !bothCheckboxesChecked,
disabled: !allCheckboxesChecked,
hidden: isFastBridgesTab
}}
>
Expand Down Expand Up @@ -157,6 +169,29 @@ export function WithdrawalConfirmationDialog(
onChange={setCheckbox2Checked}
/>

{fastWithdrawalActive && (
<Checkbox
label={
<span className="font-light">
I understand that ~{confirmationPeriod} is an estimate,
and it&apos;s possible the committee fails and it will
default back to the 8 days.{' '}
<ExternalLink
href={FAST_WITHDRAWAL_DOCS_ARTICLE_LINK}
className="underline"
onClick={e => {
e.stopPropagation()
}}
>
Learn more.
</ExternalLink>
</span>
}
checked={checkbox3Checked}
onChange={setCheckbox3Checked}
/>
)}

<div className="flex">
<SecurityGuaranteed />
</div>
Expand Down
2 changes: 2 additions & 0 deletions packages/arb-token-bridge-ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const ETH_BALANCE_ARTICLE_LINK = `${SUPPORT_LINK_BASE}/hc/en-us/articles/

export const CONFIRMATION_PERIOD_ARTICLE_LINK = `${SUPPORT_LINK_BASE}/hc/en-us/articles/18213843096091`

export const FAST_WITHDRAWAL_DOCS_ARTICLE_LINK = `${DOCS_DOMAIN}/run-arbitrum-node/arbos-releases/arbos31#additional-requirement-for-arbitrum-orbit-chains-who-wish-to-enable-fast-withdrawals`

export const ORBIT_QUICKSTART_LINK =
'https://docs.arbitrum.io/launch-orbit-chain/orbit-quickstart'

Expand Down
7 changes: 7 additions & 0 deletions packages/arb-token-bridge-ui/src/hooks/useTransferDuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getL1BlockTime,
isNetwork
} from '../util/networks'
import { getConfirmationTime } from '../util/WithdrawalUtils'

const DEPOSIT_TIME_MINUTES = {
mainnet: 15,
Expand Down Expand Up @@ -121,6 +122,12 @@ export function getWithdrawalConfirmationDate({
// For new txs createdAt won't be defined yet, we default to the current time in that case
const createdAtDate = createdAt ? dayjs(createdAt) : dayjs()

const { confirmationTimeInSeconds, fastWithdrawalActive } =
getConfirmationTime(withdrawalFromChainId)
if (fastWithdrawalActive && confirmationTimeInSeconds) {
return createdAtDate.add(confirmationTimeInSeconds, 'second')
}

const blockNumberReferenceChainId = getBlockNumberReferenceChainIdByChainId({
chainId: withdrawalFromChainId
})
Expand Down
68 changes: 68 additions & 0 deletions packages/arb-token-bridge-ui/src/util/WithdrawalUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { BigNumber } from 'ethers'
import { GasEstimates } from '../hooks/arbTokenBridge.types'
import { Address } from './AddressUtils'
import { captureSentryErrorWithExtraData } from './SentryUtils'
import { getBridgeUiConfigForChain } from './bridgeUiConfig'
import { isNetwork } from './networks'

export async function withdrawInitTxEstimateGas({
amount,
Expand Down Expand Up @@ -105,3 +107,69 @@ export async function withdrawInitTxEstimateGas({
}
}
}

const SECONDS_IN_MINUTE = 60
const SECONDS_IN_HOUR = 3600
const SECONDS_IN_DAY = 86400
const DEFAULT_CONFIRMATION_TIME = 7 * SECONDS_IN_DAY
const DEFAULT_FAST_WITHDRAWAL_TIME = SECONDS_IN_DAY
const DEFAULT_TESTNET_CONFIRMATION_TIME = SECONDS_IN_HOUR

function formatDuration(seconds: number, short = false): string {
if (seconds < SECONDS_IN_MINUTE) {
return `${seconds} ${short ? 'secs' : 'seconds'}`
}
if (seconds < SECONDS_IN_HOUR) {
return `${Math.round(seconds / SECONDS_IN_MINUTE)} ${
short ? 'mins' : 'minutes'
}`
}
if (seconds < SECONDS_IN_DAY) {
return `${Math.round(seconds / SECONDS_IN_HOUR)} hours`
}
return `${Math.round(seconds / SECONDS_IN_DAY)} days`
}

/**
* Calculate confirmation time for bridge transactions.
* @param {number} chainId - The ID of the parent chain.
*/
export function getConfirmationTime(chainId: number) {
const { fastWithdrawalTime, fastWithdrawalActive } =
getBridgeUiConfigForChain(chainId)
const isTestnet = isNetwork(chainId).isTestnet

const isDefaultConfirmationTime = !fastWithdrawalActive
const isDefaultFastWithdrawal = fastWithdrawalActive && !fastWithdrawalTime
const isCustomFastWithdrawal = fastWithdrawalActive && !!fastWithdrawalTime

let confirmationTimeInSeconds: number

if (isDefaultFastWithdrawal) {
confirmationTimeInSeconds = DEFAULT_FAST_WITHDRAWAL_TIME
} else if (isCustomFastWithdrawal) {
confirmationTimeInSeconds = fastWithdrawalTime / 1000
} else {
confirmationTimeInSeconds = isTestnet
? DEFAULT_TESTNET_CONFIRMATION_TIME
: DEFAULT_CONFIRMATION_TIME
}

const confirmationTimeInReadableFormat = formatDuration(
confirmationTimeInSeconds
)
const confirmationTimeInReadableFormatShort = formatDuration(
confirmationTimeInSeconds,
true
)

return {
fastWithdrawalActive,
isDefaultConfirmationTime,
isDefaultFastWithdrawal,
isCustomFastWithdrawal,
confirmationTimeInSeconds,
confirmationTimeInReadableFormat,
confirmationTimeInReadableFormatShort
}
}
4 changes: 3 additions & 1 deletion packages/arb-token-bridge-ui/src/util/orbitChainsData.json
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,9 @@
"symbol": "CHEESE",
"decimals": 18,
"logoUrl": "/images/CheeseChain_NativeTokenLogo.jpg"
}
},
"fastWithdrawalTime": 900000,
"fastWithdrawalActive": true
}
},
{
Expand Down
2 changes: 2 additions & 0 deletions packages/arb-token-bridge-ui/src/util/orbitChainsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export type BridgeUiConfig = {
description?: string
}
nativeTokenData?: NativeCurrencyBase
fastWithdrawalTime?: number
fastWithdrawalActive?: boolean
}

export type OrbitChainConfig = ChainWithRpcUrl & {
Expand Down

0 comments on commit c427bdd

Please sign in to comment.