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: transaction history for batch transfers #1851

Merged
merged 50 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
b3101b8
batch erc20 eth part 1
brtkx Aug 8, 2024
bc7fd44
update
brtkx Aug 9, 2024
5396c6a
flags
brtkx Aug 12, 2024
c9dfdae
Merge branch 'master' of github.com:OffchainLabs/arbitrum-token-bridg…
brtkx Aug 12, 2024
14e321c
format
brtkx Aug 12, 2024
5ed493b
fix
brtkx Aug 12, 2024
043e06b
extra eth input query params
brtkx Aug 12, 2024
2078929
clean up
brtkx Aug 13, 2024
a0af524
sanitize experiments server side
brtkx Aug 13, 2024
278172b
Merge branch 'batch-erc20-eth-1' of github.com:OffchainLabs/arbitrum-…
brtkx Aug 13, 2024
c38f030
error messages batch erc20 eth
brtkx Aug 13, 2024
cb2a1d2
max amount batched eth erc20
brtkx Aug 13, 2024
934101f
fixes
brtkx Aug 13, 2024
52d3ca1
fix
brtkx Aug 13, 2024
4ad0c88
Merge branch 'batch-erc20-eth-3' of github.com:OffchainLabs/arbitrum-…
brtkx Aug 13, 2024
563fc60
revert
brtkx Aug 13, 2024
8a434f8
comment
brtkx Aug 13, 2024
d4e3152
Merge branch 'master' of github.com:OffchainLabs/arbitrum-token-bridg…
brtkx Aug 14, 2024
4580aa9
clean up
brtkx Aug 14, 2024
7245be6
rename
brtkx Aug 14, 2024
b3728f8
limit amount2 to 18 decimals
brtkx Aug 14, 2024
fd75436
Merge branch 'batch-erc20-eth-2' of github.com:OffchainLabs/arbitrum-…
brtkx Aug 14, 2024
1c23323
Merge branch 'batch-erc20-eth-3' of github.com:OffchainLabs/arbitrum-…
brtkx Aug 14, 2024
c5c40ef
fix
brtkx Aug 14, 2024
b71428b
batch transfer
brtkx Aug 15, 2024
49fc9e9
Merge branch 'master' of github.com:OffchainLabs/arbitrum-token-bridg…
brtkx Aug 19, 2024
f5283da
tx history for batch transfers
brtkx Aug 19, 2024
32976fc
fixes
brtkx Aug 19, 2024
40601ad
Merge branch 'master' into batch-erc20-eth-6
brtkx Aug 19, 2024
a16780e
comment
brtkx Aug 19, 2024
e970c0e
nit
brtkx Aug 19, 2024
adfff86
rename
brtkx Aug 20, 2024
7971d02
update error
brtkx Aug 20, 2024
7747d8a
fix
brtkx Aug 20, 2024
d7cfbe4
fixes
brtkx Aug 20, 2024
168f4e6
update
brtkx Aug 20, 2024
a3dc81a
clean up
brtkx Aug 20, 2024
7b98e8f
clean up
brtkx Aug 20, 2024
f18cd3b
clean up comments
brtkx Aug 20, 2024
cdded8f
clean up
brtkx Aug 20, 2024
1ef679f
Merge branch 'master' into batch-erc20-eth-6
fionnachan Aug 21, 2024
10719d8
adjust gas calc
brtkx Aug 22, 2024
4fdff52
Merge branch 'batch-erc20-eth-6' of github.com:OffchainLabs/arbitrum-…
brtkx Aug 22, 2024
be8200b
fixes
brtkx Aug 22, 2024
73a4665
Merge branch 'master' into batch-erc20-eth-6
brtkx Aug 22, 2024
8f6a780
rename
brtkx Aug 22, 2024
b85ff77
Merge branch 'batch-erc20-eth-6' of github.com:OffchainLabs/arbitrum-…
brtkx Aug 22, 2024
eafaa68
fixes
brtkx Aug 22, 2024
5d6abb4
Merge branch 'master' into batch-erc20-eth-6
brtkx Aug 22, 2024
1165686
Merge branch 'master' into batch-erc20-eth-6
fionnachan Aug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ import { TransactionsTableRow } from './TransactionsTableRow'
import { EmptyTransactionHistory } from './EmptyTransactionHistory'
import { Address } from '../../util/AddressUtils'

export const EstimatedAmountTooltip = ({ children }: PropsWithChildren) => {
return (
<Tooltip content="This is an estimate amount. The final amount may have minor fluctuations based on the total gas used.">
{children}
</Tooltip>
)
}

export const ContentWrapper = ({
children,
className = ''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Image from 'next/image'
import dayjs from 'dayjs'
import CctpLogoColor from '@/images/CctpLogoColor.svg'
import ArbitrumLogo from '@/images/ArbitrumLogo.svg'
import EthereumLogoRoundLight from '@/images/EthereumLogoRoundLight.svg'

import { useTxDetailsStore } from './TransactionHistory'
import { getExplorerUrl, getNetworkName, isNetwork } from '../../util/networks'
Expand All @@ -22,6 +23,8 @@ import { shortenAddress } from '../../util/CommonUtils'
import { isTxCompleted } from './helpers'
import { Address } from '../../util/AddressUtils'
import { sanitizeTokenSymbol } from '../../util/TokenUtils'
import { isBatchTransfer } from '../../util/TokenDepositUtils'
import { EstimatedAmountTooltip } from './TransactionHistoryTable'

const DetailsBox = ({
children,
Expand Down Expand Up @@ -135,17 +138,41 @@ export const TransactionsTableDetails = ({
<span>{dayjs(tx.createdAt).format('MMMM DD, YYYY')}</span>
<span>{dayjs(tx.createdAt).format('h:mma')}</span>
</div>
<div className="flex items-center space-x-2">
<TransactionsTableTokenImage tx={tx} />
<span>
{formatAmount(Number(tx.value), {
symbol: tokenSymbol
})}
</span>
{showPriceInUsd && (
<span className="text-white/70">
{formatUSD(ethToUSD(Number(tx.value)))}
<div className="flex flex-col space-y-1">
<div className="flex items-center space-x-2">
<TransactionsTableTokenImage tx={tx} />
<span>
{formatAmount(Number(tx.value), {
symbol: tokenSymbol
})}
</span>
{showPriceInUsd && (
<span className="text-white/70">
{formatUSD(ethToUSD(Number(tx.value)))}
</span>
)}
</div>
{isBatchTransfer(tx) && (
<EstimatedAmountTooltip>
<div className="flex items-center space-x-2">
<Image
height={20}
width={20}
alt="ETH logo"
src={EthereumLogoRoundLight}
/>
<span className="ml-2">
{formatAmount(Number(tx.value2), {
symbol: ether.symbol
})}
</span>
{isNetwork(tx.sourceChainId).isEthereumMainnet && (
<span className="text-white/70">
{formatUSD(ethToUSD(Number(tx.value2)))}
</span>
)}
</div>
</EstimatedAmountTooltip>
)}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
CheckCircleIcon,
XCircleIcon
} from '@heroicons/react/24/outline'
import EthereumLogoRoundLight from '@/images/EthereumLogoRoundLight.svg'
import Image from 'next/image'

import { DepositStatus, MergedTransaction } from '../../state/app/state'
import { formatAmount } from '../../util/NumberUtils'
Expand All @@ -28,6 +30,9 @@ import { TransactionsTableTokenImage } from './TransactionsTableTokenImage'
import { useTxDetailsStore } from './TransactionHistory'
import { TransactionsTableExternalLink } from './TransactionsTableExternalLink'
import { Address } from '../../util/AddressUtils'
import { ether } from '../../constants'
import { isBatchTransfer } from '../../util/TokenDepositUtils'
import { EstimatedAmountTooltip } from './TransactionHistoryTable'

const StatusLabel = ({ tx }: { tx: MergedTransaction }) => {
const { sourceChainId, destinationChainId } = tx
Expand Down Expand Up @@ -170,18 +175,37 @@ export function TransactionsTableRow({
)}
>
<div className="pr-3 align-middle">{txRelativeTime}</div>
<div className="flex items-center pr-3 align-middle">
<TransactionsTableExternalLink
href={`${getExplorerUrl(sourceChainId)}/token/${tx.tokenAddress}`}
disabled={!tx.tokenAddress}
>
<TransactionsTableTokenImage tx={tx} />
<span className="ml-2">
{formatAmount(Number(tx.value), {
symbol: tokenSymbol
})}
</span>
</TransactionsTableExternalLink>
<div className="flex flex-col space-y-1">
<div className="flex items-center pr-3 align-middle">
<TransactionsTableExternalLink
href={`${getExplorerUrl(sourceChainId)}/token/${tx.tokenAddress}`}
disabled={!tx.tokenAddress}
>
<TransactionsTableTokenImage tx={tx} />
<span className="ml-2">
{formatAmount(Number(tx.value), {
symbol: tokenSymbol
})}
</span>
</TransactionsTableExternalLink>
</div>
{isBatchTransfer(tx) && (
<EstimatedAmountTooltip>
<div className="flex items-center pr-3 align-middle">
<Image
height={20}
width={20}
alt="ETH logo"
src={EthereumLogoRoundLight}
/>
<span className="ml-2">
{formatAmount(Number(tx.value2), {
symbol: ether.symbol
})}
</span>
</div>
</EstimatedAmountTooltip>
)}
</div>
<div className="flex items-center space-x-2">
<TransactionsTableExternalLink
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import dayjs from 'dayjs'
import { useState, useMemo } from 'react'
import Tippy from '@tippyjs/react'
import { BigNumber, constants, utils } from 'ethers'
import { constants, utils } from 'ethers'
import { useLatest } from 'react-use'
import { useAccount, useChainId, useSigner } from 'wagmi'
import { TransactionResponse } from '@ethersproject/providers'
Expand Down Expand Up @@ -930,6 +930,8 @@ export function TransferPanel() {

const { sourceChainTransaction } = bridgeTransfer

const isBatchTransfer = isBatchTransferSupported && Number(amount2) > 0

const timestampCreated = Math.floor(Date.now() / 1000).toString()

const txHistoryCompatibleObject = convertBridgeSdkToMergedTransaction({
Expand All @@ -941,6 +943,7 @@ export function TransferPanel() {
destinationAddress,
nativeCurrency,
amount: amountBigNumber,
amount2: isBatchTransfer ? utils.parseEther(amount2) : undefined,
timestampCreated
})

Expand All @@ -959,6 +962,7 @@ export function TransferPanel() {
destinationAddress,
nativeCurrency,
amount: amountBigNumber,
amount2: isBatchTransfer ? utils.parseEther(amount2) : undefined,
timestampCreated
})
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type SdkToUiConversionProps = {
destinationAddress?: string
nativeCurrency: NativeCurrency
amount: BigNumber
amount2?: BigNumber
timestampCreated: string
}

Expand All @@ -34,7 +35,8 @@ export const convertBridgeSdkToMergedTransaction = ({
walletAddress,
destinationAddress,
nativeCurrency,
amount
amount,
amount2
}: SdkToUiConversionProps): MergedTransaction => {
const { transferType } = bridgeTransfer
const isDeposit =
Expand All @@ -57,6 +59,7 @@ export const convertBridgeSdkToMergedTransaction = ({
amount,
selectedToken ? selectedToken.decimals : nativeCurrency.decimals
),
value2: amount2 ? utils.formatEther(amount2) : undefined,
depositStatus: isDeposit ? DepositStatus.L1_PENDING : undefined,
uniqueId: null,
isWithdrawal: !isDeposit,
Expand All @@ -78,6 +81,7 @@ export const convertBridgeSdkToPendingDepositTransaction = ({
nativeCurrency,
destinationAddress,
amount,
amount2,
timestampCreated
}: SdkToUiConversionProps): Deposit => {
const transaction =
Expand All @@ -95,6 +99,7 @@ export const convertBridgeSdkToPendingDepositTransaction = ({
amount,
selectedToken ? selectedToken.decimals : nativeCurrency.decimals
),
value2: amount2 ? utils.formatEther(amount2) : undefined,
parentChainId,
childChainId,
direction: 'deposit',
Expand Down
1 change: 1 addition & 0 deletions packages/arb-token-bridge-ui/src/hooks/useTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ type TransactionBase = {
type: TxnType
status?: TxnStatus
value: string | null
value2?: string
txID?: string
assetName: string
assetType: AssetType
Expand Down
1 change: 1 addition & 0 deletions packages/arb-token-bridge-ui/src/state/app/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface MergedTransaction {
asset: string
assetType: AssetType
value: string | null
value2?: string
uniqueId: BigNumber | null
isWithdrawal: boolean
blockNum: number | null
Expand Down
1 change: 1 addition & 0 deletions packages/arb-token-bridge-ui/src/state/app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export const transformDeposit = (
asset: tx.assetName || '',
assetType: tx.assetType,
value: tx.value,
value2: tx.value2,
uniqueId: null, // not needed
isWithdrawal: false,
blockNum: tx.blockNumber || null,
Expand Down
15 changes: 14 additions & 1 deletion packages/arb-token-bridge-ui/src/util/TokenDepositUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {
fetchErc20ParentChainGatewayAddress,
getL2ERC20Address
} from './TokenUtils'
import { DepositGasEstimates } from '../hooks/arbTokenBridge.types'
import { AssetType, DepositGasEstimates } from '../hooks/arbTokenBridge.types'
import { addressIsSmartContract } from './AddressUtils'
import { getChainIdFromProvider } from '../token-bridge-sdk/utils'
import { captureSentryErrorWithExtraData } from './SentryUtils'
import { MergedTransaction } from '../state/app/state'
import { isExperimentalFeatureEnabled } from '.'

async function fetchTokenFallbackGasEstimates({
inboxAddress,
Expand Down Expand Up @@ -216,3 +218,14 @@ async function addressIsCustomGatewayToken({
childChainNetwork.tokenBridge?.parentCustomGateway.toLowerCase()
)
}

export function isBatchTransfer(tx: MergedTransaction) {
return (
isExperimentalFeatureEnabled('batch') &&
!tx.isCctp &&
!tx.isWithdrawal &&
tx.assetType === AssetType.ERC20 &&
typeof tx.value2 !== 'undefined' &&
Number(tx.value2) > 0
)
}
85 changes: 83 additions & 2 deletions packages/arb-token-bridge-ui/src/util/deposits/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
EthL1L3DepositStatus,
Erc20L1L3DepositStatus
} from '@arbitrum/sdk'
import { utils } from 'ethers'

import { Provider } from '@ethersproject/providers'
import { AssetType } from '../../hooks/arbTokenBridge.types'
Expand All @@ -26,6 +27,10 @@ import {
} from '../../token-bridge-sdk/teleport'
import { getProviderForChainId } from '../../token-bridge-sdk/utils'

// max amount that will be consider gas only for max submission fee
// anything above that we assume users sent extra ETH in amount2
const MAX_SUBMISSION_FEE_THRESHOLD = 0.0001

export const updateAdditionalDepositData = async ({
depositTx,
l1Provider,
Expand Down Expand Up @@ -110,14 +115,90 @@ export const updateAdditionalDepositData = async ({
})
}

// finally, else if the transaction is not ETH ie. it's a ERC20 token deposit
return updateTokenDepositStatusData({
// ERC-20 deposit
const tokenDeposit = await updateTokenDepositStatusData({
depositTx,
l1ToL2Msg: l1ToL2Msg as ParentToChildMessageReader,
timestampCreated,
l1Provider,
l2Provider
})

// check local storage first, fallback to fetching on chain
if (depositTx.value2) {
return { ...tokenDeposit, value2: depositTx.value2 }
}

const { value2 } = await getBatchTransferDepositData({
l1ToL2Msg: l1ToL2Msg as ParentToChildMessageReader,
depositStatus: depositTx.status
})

return {
...tokenDeposit,
value2
}
}

const getBatchTransferDepositData = async ({
l1ToL2Msg,
depositStatus
}: {
l1ToL2Msg: ParentToChildMessageReader
depositStatus: TxnStatus | undefined
}): Promise<{
value2: Transaction['value2']
}> => {
let value2: Transaction['value2']

// get maxSubmissionCost, which is the amount of ETH sent in batched ERC-20 deposit + max gas cost
const maxSubmissionCost = Number(
utils.formatEther(l1ToL2Msg.messageData.maxSubmissionFee.toString())
)

// we deduct gas cost from max submission fee, which leaves us with amount2 (extra ETH sent with ERC-20)
if (depositStatus === 'success') {
spsjvc marked this conversation as resolved.
Show resolved Hide resolved
// if success, we use the actual gas cost
const gasCost = await getRetryableExecutionTxFeeOnChildChain({
l1ToL2Msg
})

if (!gasCost) {
return { value2: undefined }
}

value2 = String(Number(maxSubmissionCost) - Number(gasCost))
} else {
// when not success, we don't know the final gas cost yet so we use estimates
const estimatedGasCost = utils.formatEther(
l1ToL2Msg.messageData.gasLimit.mul(l1ToL2Msg.messageData.maxFeePerGas)
)

value2 = String(Number(maxSubmissionCost) - Number(estimatedGasCost))
}

if (Number(value2) < MAX_SUBMISSION_FEE_THRESHOLD) {
// ETH amount too little to distinguish between gas used, won't show
return { value2: undefined }
}

return { value2 }
}

const getRetryableExecutionTxFeeOnChildChain = async ({
l1ToL2Msg
}: {
l1ToL2Msg: ParentToChildMessageReader
}) => {
const childReceipt = await l1ToL2Msg.getAutoRedeemAttempt()
Copy link
Member

Choose a reason for hiding this comment

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

not sure if this will work if the retryable was manually redeemed


if (!childReceipt) {
return undefined
}

const { gasUsed, effectiveGasPrice } = childReceipt

return utils.formatEther(gasUsed.mul(effectiveGasPrice))
}

const updateETHDepositStatusData = async ({
Expand Down
Loading