Skip to content

Commit

Permalink
fix: withdrawal tx summary gas est. & hints update (#1234)
Browse files Browse the repository at this point in the history
  • Loading branch information
fionnachan authored Nov 14, 2023
1 parent ab3332f commit cabd048
Show file tree
Hide file tree
Showing 16 changed files with 193 additions and 120 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ yarn-error.log
.DS_Store
tsconfig.tsbuildinfo
__auto-generated-denylist.json

4 changes: 2 additions & 2 deletions packages/arb-token-bridge-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"graphql": "^16.8.1",
"lodash-es": "^4.17.21",
"next": "^13.5.6",
"next-query-params": "^4.1.0",
"next-query-params": "^4.2.3",
"overmind": "^28.0.1",
"overmind-react": "^29.0.1",
"posthog-js": "^1.57.2",
Expand Down Expand Up @@ -112,6 +112,6 @@
"tailwindcss": "^3.2.4",
"ts-node": "^10.9.1",
"ts-prune": "^0.10.1",
"typescript": "^4.7.4"
"typescript": "^5.2.2"
}
}
1 change: 1 addition & 0 deletions packages/arb-token-bridge-ui/scripts/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
},
"compilerOptions": {
// typescript options here
"moduleResolution": "NodeNext"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ export function TransferPanelMain({

return { ...result, estimatedL2SubmissionCost: constants.Zero }
},
[isDepositMode, walletAddress, l1.provider, l2.provider]
[walletAddress, isDepositMode, l2.provider, l1.provider]
)

const setMaxAmount = useCallback(async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import React, { useEffect, useMemo, useState } from 'react'
import { BigNumber, constants, utils } from 'ethers'
import { InformationCircleIcon } from '@heroicons/react/24/outline'
import { useLatest } from 'react-use'
import { useAccount } from 'wagmi'

import { Tooltip } from '../common/Tooltip'
import { useAppState } from '../../state'
import { useETHPrice } from '../../hooks/useETHPrice'
import { useDebouncedValue } from '../../hooks/useDebouncedValue'
import { formatAmount, formatUSD } from '../../util/NumberUtils'
import { isNetwork } from '../../util/networks'
import { getNetworkName, isNetwork } from '../../util/networks'
import { useNetworksAndSigners } from '../../hooks/useNetworksAndSigners'
import { tokenRequiresApprovalOnL2 } from '../../util/L2ApprovalUtils'
import { useGasPrice } from '../../hooks/useGasPrice'
import { depositTokenEstimateGas } from '../../util/TokenDepositUtils'
import { depositEthEstimateGas } from '../../util/EthDepositUtils'
Expand Down Expand Up @@ -44,12 +42,21 @@ export type UseGasSummaryResult = {
estimatedL2GasFees: number
}

const layerToGasFeeTooltip: { [key in ChainLayer]: string } = {
L1: 'L1 fees go to Ethereum Validators.',
L2: "L2 fees are collected by the chain to cover costs of execution. This is an estimated fee, if the true fee is lower you'll be refunded.",
Orbit:
"Orbit fees are collected by the chain to cover costs of execution. This is an estimated fee, if the true fee is lower you'll be refunded."
}
const depositGasFeeTooltip = ({
l1NetworkName,
l2NetworkName,
depositToOrbit = false
}: {
l1NetworkName: string
l2NetworkName: string
depositToOrbit?: boolean
}) => ({
L1: `${l1NetworkName} fees go to Ethereum Validators.`,
L2: `${
depositToOrbit ? l1NetworkName : l2NetworkName
} fees are collected by the chain to cover costs of execution. This is an estimated fee, if the true fee is lower, you'll be refunded.`,
Orbit: `${l2NetworkName} fees are collected by the chain to cover costs of execution. This is an estimated fee, if the true fee is lower, you'll be refunded.`
})

export function useGasSummary(
amount: BigNumber,
Expand All @@ -61,7 +68,6 @@ export function useGasSummary(
} = useAppState()
const networksAndSigners = useNetworksAndSigners()
const { l1, l2 } = networksAndSigners
const latestNetworksAndSigners = useLatest(networksAndSigners)
const { address: walletAddress } = useAccount()

const l1GasPrice = useGasPrice({ provider: l1.provider })
Expand All @@ -81,10 +87,10 @@ export function useGasSummary(
})

// Estimated L1 gas fees, denominated in Ether, represented as a floating point number
const estimatedL1GasFees = useMemo(
() => parseFloat(utils.formatEther(result.estimatedL1Gas.mul(l1GasPrice))),
[result.estimatedL1Gas, l1GasPrice]
)
const estimatedL1GasFees = useMemo(() => {
const gasPrice = isDepositMode ? l1GasPrice : l2GasPrice
return parseFloat(utils.formatEther(result.estimatedL1Gas.mul(gasPrice)))
}, [result.estimatedL1Gas, isDepositMode, l1GasPrice, l2GasPrice])

// Estimated L2 gas fees, denominated in Ether, represented as a floating point number
const estimatedL2GasFees = useMemo(
Expand Down Expand Up @@ -156,18 +162,7 @@ export function useGasSummary(
estimatedL2Gas: BigNumber
}

// TODO: Update, as this only handles LPT
if (
tokenRequiresApprovalOnL2(
token.address,
latestNetworksAndSigners.current.l2.network.id
)
) {
estimateGasResult = {
estimatedL1Gas: BigNumber.from(5_000),
estimatedL2Gas: BigNumber.from(10_000)
}
} else if (
isTokenArbitrumOneNativeUSDC(token.address) ||
isTokenArbitrumGoerliNativeUSDC(token.address)
) {
Expand Down Expand Up @@ -274,28 +269,44 @@ export function TransferPanelSummary({
}: TransferPanelSummaryProps) {
const { status, estimatedL1GasFees, estimatedL2GasFees } = gasSummary

const { app } = useAppState()
const {
app: { isDepositMode }
} = useAppState()
const { ethToUSD } = useETHPrice()
const { l1, l2 } = useNetworksAndSigners()
const { parentLayer, layer } = useChainLayers()

const nativeCurrency = useNativeCurrency({ provider: l2.provider })
const parentChainNativeCurrency = useNativeCurrency({ provider: l1.provider })

const layerGasFeeTooltipContent = (layer: ChainLayer) => {
if (!isDepositMode) {
return null
}

const { isOrbitChain: isDepositToOrbitChain } = isNetwork(l2.network.id)

return depositGasFeeTooltip({
l1NetworkName: getNetworkName(l1.network.id),
l2NetworkName: getNetworkName(l2.network.id),
depositToOrbit: isDepositToOrbitChain
})[layer]
}

const isBridgingETH = token === null && !nativeCurrency.isCustom
const showPrice = isBridgingETH && !isNetwork(l1.network.id).isTestnet
const showBreakdown = !nativeCurrency.isCustom
const showBreakdown = !nativeCurrency.isCustom && isDepositMode

const tokenSymbol = useMemo(() => {
if (token) {
return sanitizeTokenSymbol(token.symbol, {
erc20L1Address: token.address,
chain: app.isDepositMode ? l1.network : l2.network
chain: isDepositMode ? l1.network : l2.network
})
}

return nativeCurrency.symbol
}, [token, nativeCurrency, app.isDepositMode, l1.network, l2.network])
}, [token, nativeCurrency, isDepositMode, l1.network, l2.network])

const sameNativeCurrency = useMemo(
// we'll have to change this if we ever have L4s that are built on top of L3s with a custom fee token
Expand All @@ -309,7 +320,7 @@ export function TransferPanelSummary({
)

if (status === 'loading') {
const bgClassName = app.isDepositMode ? 'bg-ocl-blue' : 'bg-eth-dark'
const bgClassName = isDepositMode ? 'bg-ocl-blue' : 'bg-eth-dark'

return (
<TransferPanelSummaryContainer className="animate-pulse">
Expand Down Expand Up @@ -359,8 +370,8 @@ export function TransferPanelSummary({
return (
<TransferPanelSummaryContainer>
<div className="flex flex-row justify-between text-sm text-gray-dark lg:text-base">
<span className="w-2/5 font-light">You&apos;re moving</span>
<div className="flex w-3/5 flex-row justify-between">
<span className="w-3/5 font-light">You&apos;re moving</span>
<div className="flex w-2/5 flex-row justify-between">
<span>
{formatAmount(amount, {
symbol: tokenSymbol
Expand All @@ -376,8 +387,10 @@ export function TransferPanelSummary({
</div>

<div className="flex flex-row items-center justify-between text-sm text-gray-dark lg:text-base">
<span className="w-2/5 font-light">You&apos;ll pay in gas fees</span>
<div className="flex w-3/5 justify-between">
<span className="w-3/5 font-light">
You&apos;ll now pay in gas fees
</span>
<div className="flex w-2/5 justify-between">
{sameNativeCurrency ? (
<>
<span>
Expand Down Expand Up @@ -411,11 +424,11 @@ export function TransferPanelSummary({
<div className="flex flex-row justify-between">
<div className="flex flex-row items-center space-x-2">
<span className="pl-4 font-light">{parentLayer} gas</span>
<Tooltip content={layerToGasFeeTooltip[parentLayer]}>
<Tooltip content={layerGasFeeTooltipContent(parentLayer)}>
<InformationCircleIcon className="h-4 w-4" />
</Tooltip>
</div>
<div className="flex w-3/5 flex-row justify-between">
<div className="flex w-2/5 flex-row justify-between">
<span className="font-light">
{formatAmount(estimatedL1GasFees, {
symbol: parentChainNativeCurrency.symbol
Expand All @@ -432,11 +445,11 @@ export function TransferPanelSummary({
<div className="flex flex-row justify-between text-gray-dark">
<div className="flex flex-row items-center space-x-2">
<span className="pl-4 font-light ">{layer} gas</span>
<Tooltip content={layerToGasFeeTooltip[layer]}>
<Tooltip content={layerGasFeeTooltipContent(layer)}>
<InformationCircleIcon className="h-4 w-4 " />
</Tooltip>
</div>
<div className="flex w-3/5 flex-row justify-between">
<div className="flex w-2/5 flex-row justify-between">
<span className="font-light">
{formatAmount(estimatedL2GasFees, {
symbol: nativeCurrency.symbol
Expand All @@ -453,30 +466,22 @@ export function TransferPanelSummary({
</div>
)}

{isBridgingETH && (
{!isDepositMode && (
<>
<div>
<div className="h-2" />
<div className="border-b border-gray-5" />
<div className="h-2" />
</div>
<div className="flex flex-row justify-between text-sm text-gray-dark lg:text-base">
<span className="w-2/5 font-light text-gray-dark">
Total amount
</span>
<div className="flex w-3/5 flex-row justify-between">
<span>
{formatAmount(amount + estimatedTotalGasFees, {
symbol: nativeCurrency.symbol
})}
</span>

{showPrice && (
<span className="font-medium text-dark">
{formatUSD(ethToUSD(amount + estimatedTotalGasFees))}
</span>
)}
</div>
<div className="flex flex-col gap-3 text-sm font-light text-gray-dark lg:text-base">
<p>
This transaction will initiate the withdrawal on {l2.network.name}
.
</p>
<p>
When the withdrawal is ready for claiming on {l1.network.name},
you will have to pay gas fees for the claim transaction.
</p>
</div>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export function Tooltip({
wrapperClassName = 'w-max',
theme = 'light',
children
}: TooltipProps): JSX.Element {
}: TooltipProps): JSX.Element | null {
if (!content) {
return null
}

if (!show) {
return <>{children}</>
}
Expand Down
49 changes: 42 additions & 7 deletions packages/arb-token-bridge-ui/src/util/EthWithdrawalUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { EthBridger } from '@arbitrum/sdk'
import { Provider } from '@ethersproject/providers'
import { BigNumber } from 'ethers'
import * as Sentry from '@sentry/react'
import { NodeInterface__factory } from '@arbitrum/sdk/dist/lib/abi/factories/NodeInterface__factory'
import { NODE_INTERFACE_ADDRESS } from '@arbitrum/sdk/dist/lib/dataEntities/constants'

import { GasEstimates } from '../hooks/arbTokenBridge.types'

export async function withdrawEthEstimateGas({
Expand All @@ -9,7 +13,7 @@ export async function withdrawEthEstimateGas({
l2Provider
}: {
amount: BigNumber
address: string
address: `0x${string}`
l2Provider: Provider
}): Promise<GasEstimates> {
const ethBridger = await EthBridger.fromProvider(l2Provider)
Expand All @@ -20,12 +24,43 @@ export async function withdrawEthEstimateGas({
from: address
})

// Can't do this atm. Hardcoded to 130_000.
const estimatedL1Gas = BigNumber.from(130_000)

const estimatedL2Gas = await l2Provider.estimateGas(
withdrawalRequest.txRequest
const nodeInterface = NodeInterface__factory.connect(
NODE_INTERFACE_ADDRESS,
l2Provider
)

return { estimatedL1Gas, estimatedL2Gas }
try {
const gasComponents = await nodeInterface.callStatic.gasEstimateComponents(
address,
false,
withdrawalRequest.txRequest.data
)
// This is the gas needed to pay for the batch posting fee
const estimatedL1Gas = BigNumber.from(gasComponents.gasEstimateForL1)

// add 30% to the estimated total gas as buffer
const estimatedTotalGas = BigNumber.from(
Math.ceil(Number(gasComponents.gasEstimate) * 1.3)
)

const estimatedL2Gas = estimatedTotalGas.sub(estimatedL1Gas)

return { estimatedL1Gas, estimatedL2Gas }
} catch (error) {
Sentry.captureException(error)
const estimatedL1Gas = BigNumber.from(130_000)
// figures based on gas estimation returned
const estimatedTotalGas = await l2Provider
.estimateGas(withdrawalRequest.txRequest)
.catch(error => {
Sentry.captureException(error)
// from recent gas estimation
return BigNumber.from(4_300_000)
})

return {
estimatedL1Gas,
estimatedL2Gas: estimatedTotalGas.sub(estimatedL1Gas)
}
}
}
8 changes: 8 additions & 0 deletions packages/arb-token-bridge-ui/src/util/NumberUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ export const formatAmount = <T extends number | BigNumber>(

// Small number, show 4 or 5 decimals based on token name length
if (value < 1) {
const maximumFractionDigits = isShortSymbol
? MaximumFractionDigits.Long
: MaximumFractionDigits.Standard
const minDisplayValue = Math.pow(10, -maximumFractionDigits)
if (value < minDisplayValue) {
return `< 0.${'0'.repeat(maximumFractionDigits - 1)}1${suffix}`
}

return (
formatNumber(value, {
maximumFractionDigits: isShortSymbol
Expand Down
Loading

1 comment on commit cabd048

@vercel
Copy link

@vercel vercel bot commented on cabd048 Nov 14, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.