diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetails.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetails.tsx index d06b766e5f..a4122e6275 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetails.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetails.tsx @@ -27,6 +27,7 @@ import { sanitizeTokenSymbol } from '../../util/TokenUtils' import { isBatchTransfer } from '../../util/TokenDepositUtils' import { BatchTransferNativeTokenTooltip } from './TransactionHistoryTable' import { useNativeCurrency } from '../../hooks/useNativeCurrency' +import { isCustomDestinationAddressTx } from '../../state/app/utils' const DetailsBox = ({ children, @@ -82,8 +83,10 @@ export const TransactionsTableDetails = ({ const isDifferentSourceAddress = address.toLowerCase() !== tx.sender?.toLowerCase() - const isDifferentDestinationAddress = - address.toLowerCase() !== tx.destination?.toLowerCase() + const isDifferentDestinationAddress = isCustomDestinationAddressTx({ + sender: address, + destination: tx.destination + }) const { sourceChainId, destinationChainId } = tx diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts b/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts index 51d7f0001f..c8b70f7957 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts @@ -22,7 +22,10 @@ import { fetchTeleporterDepositStatusData } from '../../util/deposits/helpers' import { AssetType } from '../../hooks/arbTokenBridge.types' -import { getDepositStatus } from '../../state/app/utils' +import { + getDepositStatus, + isCustomDestinationAddressTx +} from '../../state/app/utils' import { getBlockBeforeConfirmation } from '../../state/cctpState' import { getAttestationHashAndMessageFromReceipt } from '../../util/cctp/getAttestationHashAndMessageFromReceipt' import { getOutgoingMessageState } from '../../util/withdrawals/helpers' @@ -282,7 +285,7 @@ export async function getUpdatedEthDeposit( const { parentToChildMsg } = await getParentToChildMessageDataFromParentTxHash({ depositTxId: tx.txId, - isEthDeposit: true, + isRetryableDeposit: false, parentProvider: getProviderForChainId(tx.parentChainId), childProvider: getProviderForChainId(tx.childChainId) }) @@ -326,12 +329,16 @@ export async function getUpdatedEthDeposit( } } -export async function getUpdatedTokenDeposit( +export async function getUpdatedRetryableDeposit( tx: MergedTransaction ): Promise { + const isDifferentDestinationAddress = isCustomDestinationAddressTx(tx) + if ( !isTxPending(tx) || - tx.assetType !== AssetType.ERC20 || + // ETH transfer to the same address + // ETH sent to a custom destination uses retryables so we allow it in this flow + (tx.assetType === AssetType.ETH && !isDifferentDestinationAddress) || tx.isWithdrawal || tx.isCctp ) { @@ -341,7 +348,7 @@ export async function getUpdatedTokenDeposit( const { parentToChildMsg } = await getParentToChildMessageDataFromParentTxHash({ depositTxId: tx.txId, - isEthDeposit: false, + isRetryableDeposit: true, parentProvider: getProviderForChainId(tx.parentChainId), childProvider: getProviderForChainId(tx.childChainId) }) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain.tsx index 9b768e71a1..398482041a 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain.tsx @@ -27,6 +27,7 @@ import { useUpdateUSDCTokenData } from './TransferPanelMain/hooks' import { useBalances } from '../../hooks/useBalances' import { DestinationNetworkBox } from './TransferPanelMain/DestinationNetworkBox' import { SourceNetworkBox } from './TransferPanelMain/SourceNetworkBox' +import { isExperimentalFeatureEnabled } from '../../util' export function SwitchNetworksButton( props: React.ButtonHTMLAttributes @@ -281,6 +282,11 @@ export function TransferPanelMain() { ]) useEffect(() => { + if (isExperimentalFeatureEnabled('eth-custom-dest')) { + // do not reset destination address for this feature + // this will also be the default behavior when the feature is live - which means we will remove this hook + return + } // Different destination address only allowed for tokens if (!selectedToken) { setDestinationAddress(undefined) diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 86eb200d34..7dc151bbaa 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -20,6 +20,7 @@ import { import { isTeleportTx, Transaction } from './useTransactions' import { MergedTransaction } from '../state/app/state' import { + isCustomDestinationAddressTx, normalizeTimestamp, transformDeposit, transformWithdrawal @@ -39,7 +40,7 @@ import { getUpdatedCctpTransfer, getUpdatedEthDeposit, getUpdatedTeleportTransfer, - getUpdatedTokenDeposit, + getUpdatedRetryableDeposit, getUpdatedWithdrawal, isCctpTransfer, isSameTransaction, @@ -733,16 +734,18 @@ export const useTransactionHistory = ( return } - // ETH deposit - if (tx.assetType === AssetType.ETH) { + const isDifferentDestinationAddress = isCustomDestinationAddressTx(tx) + + // ETH deposit to the same address + if (tx.assetType === AssetType.ETH && !isDifferentDestinationAddress) { const updatedEthDeposit = await getUpdatedEthDeposit(tx) updateCachedTransaction(updatedEthDeposit) return } - // Token deposit - const updatedTokenDeposit = await getUpdatedTokenDeposit(tx) - updateCachedTransaction(updatedTokenDeposit) + // Token deposit or ETH deposit to a different destination address + const updatedRetryableDeposit = await getUpdatedRetryableDeposit(tx) + updateCachedTransaction(updatedRetryableDeposit) }, [updateCachedTransaction] ) diff --git a/packages/arb-token-bridge-ui/src/state/app/utils.ts b/packages/arb-token-bridge-ui/src/state/app/utils.ts index 5ed645ba87..b2a14492df 100644 --- a/packages/arb-token-bridge-ui/src/state/app/utils.ts +++ b/packages/arb-token-bridge-ui/src/state/app/utils.ts @@ -94,6 +94,9 @@ export const getDepositStatus = ( } } + const isNativeTokenTransferToSameAddress = + tx.assetType === AssetType.ETH && !isCustomDestinationAddressTx(tx) + const { parentToChildMsgData: l1ToL2MsgData } = tx if (!l1ToL2MsgData) { return DepositStatus.L2_PENDING @@ -104,11 +107,11 @@ export const getDepositStatus = ( case ParentToChildMessageStatus.CREATION_FAILED: return DepositStatus.CREATION_FAILED case ParentToChildMessageStatus.EXPIRED: - return tx.assetType === AssetType.ETH + return isNativeTokenTransferToSameAddress ? DepositStatus.L2_SUCCESS : DepositStatus.EXPIRED case ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHILD: { - return tx.assetType === AssetType.ETH + return isNativeTokenTransferToSameAddress ? DepositStatus.L2_SUCCESS : DepositStatus.L2_FAILURE } diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthDepositStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthDepositStarter.ts index fb7f8945c8..c8bc3e3e1e 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthDepositStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthDepositStarter.ts @@ -13,6 +13,7 @@ import { getAddressFromSigner, percentIncrease } from './utils' import { depositEthEstimateGas } from '../util/EthDepositUtils' import { fetchErc20Allowance } from '../util/TokenUtils' import { isExperimentalFeatureEnabled } from '../util' +import { isCustomDestinationAddressTx } from '../state/app/utils' export class EthDepositStarter extends BridgeTransferStarter { public transferType: TransferType = 'eth_deposit' @@ -106,9 +107,10 @@ export class EthDepositStarter extends BridgeTransferStarter { const address = await getAddressFromSigner(signer) const ethBridger = await this.getBridger() - const isDifferentDestinationAddress = - destinationAddress && - destinationAddress.toLowerCase() !== address.toLowerCase() + const isDifferentDestinationAddress = isCustomDestinationAddressTx({ + sender: address, + destination: destinationAddress + }) // TODO: remove this when eth-custom-dest feature is live // this is a safety check, this shouldn't happen @@ -125,7 +127,8 @@ export class EthDepositStarter extends BridgeTransferStarter { from: address, parentProvider: this.sourceChainProvider, childProvider: this.destinationChainProvider, - destinationAddress + // we know it's defined + destinationAddress: String(destinationAddress) }) : await ethBridger.getDepositRequest({ amount, @@ -145,7 +148,7 @@ export class EthDepositStarter extends BridgeTransferStarter { amount, parentSigner: signer, childProvider: this.destinationChainProvider, - destinationAddress, + destinationAddress: String(destinationAddress), overrides: parentChainOverrides, retryableGasOverrides: { // the gas limit may vary by about 20k due to SSTORE (zero vs nonzero) diff --git a/packages/arb-token-bridge-ui/src/util/EthDepositUtils.ts b/packages/arb-token-bridge-ui/src/util/EthDepositUtils.ts index 4054f64b0c..d6b1563350 100644 --- a/packages/arb-token-bridge-ui/src/util/EthDepositUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/EthDepositUtils.ts @@ -4,6 +4,7 @@ import { BigNumber, constants } from 'ethers' import { DepositGasEstimates } from '../hooks/arbTokenBridge.types' import { fetchErc20Allowance } from './TokenUtils' import { DepositTxEstimateGasParams } from './TokenDepositUtils' +import { isCustomDestinationAddressTx } from '../state/app/utils' function fetchFallbackGasEstimatesForOrbitChainWithCustomFeeToken(): DepositGasEstimates { return { @@ -61,9 +62,10 @@ export async function depositEthEstimateGas( return fetchFallbackGasEstimatesForOrbitChainWithCustomFeeToken() } - const isDifferentDestinationAddress = - destinationAddress && - destinationAddress.toLowerCase() !== address.toLowerCase() + const isDifferentDestinationAddress = isCustomDestinationAddressTx({ + sender: address, + destination: destinationAddress + }) if (isDifferentDestinationAddress) { const depositToRequest = await ethBridger.getDepositToRequest({ @@ -71,7 +73,8 @@ export async function depositEthEstimateGas( from: address, parentProvider: parentChainProvider, childProvider: childChainProvider, - destinationAddress + // we know it's defined + destinationAddress: String(destinationAddress) }) const estimatedParentChainGas = await parentChainProvider.estimateGas( diff --git a/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts b/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts index 72b7ba2e9d..53781fd739 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts @@ -26,7 +26,10 @@ import { isValidTeleportChainPair } from '../../token-bridge-sdk/teleport' import { getProviderForChainId } from '../../token-bridge-sdk/utils' -import { normalizeTimestamp } from '../../state/app/utils' +import { + isCustomDestinationAddressTx, + normalizeTimestamp +} from '../../state/app/utils' export const updateAdditionalDepositData = async ({ depositTx, @@ -60,14 +63,22 @@ export const updateAdditionalDepositData = async ({ const { isClassic } = depositTx // isClassic is known before-hand from subgraphs - const isEthDeposit = depositTx.assetType === AssetType.ETH + const isRetryableDeposit = + depositTx.assetType === AssetType.ERC20 || + // we use `depositTo` from arbitrum-sdk to send native token to a different destination address + // it uses retryables so technically it's not ETH deposit + (depositTx.assetType === AssetType.ETH && + isCustomDestinationAddressTx({ + sender: depositTx.sender, + destination: depositTx.destination + })) const { parentToChildMsg } = await getParentToChildMessageDataFromParentTxHash({ depositTxId: depositTx.txID, parentProvider, childProvider, - isEthDeposit, + isRetryableDeposit, isClassic }) @@ -100,14 +111,14 @@ export const updateAdditionalDepositData = async ({ return updateClassicDepositStatusData({ depositTx, parentToChildMsg: parentToChildMsg as ParentToChildMessageReaderClassic, - isEthDeposit, + isRetryableDeposit, timestampCreated, childProvider }) } - // Check if deposit is ETH - if (isEthDeposit) { + // Check if deposit is ETH (to the same address) + if (!isRetryableDeposit) { return updateETHDepositStatusData({ depositTx, ethDepositMessage: parentToChildMsg as EthDepositMessage, @@ -116,7 +127,7 @@ export const updateAdditionalDepositData = async ({ }) } - // ERC-20 deposit + // ERC-20 deposit or ETH to a custom address const tokenDeposit = await updateTokenDepositStatusData({ depositTx, parentToChildMsg: parentToChildMsg as ParentToChildMessageReader, @@ -368,14 +379,14 @@ const updateTokenDepositStatusData = async ({ const updateClassicDepositStatusData = async ({ depositTx, parentToChildMsg, - isEthDeposit, + isRetryableDeposit, timestampCreated, childProvider }: { depositTx: Transaction timestampCreated: string childProvider: Provider - isEthDeposit: boolean + isRetryableDeposit: boolean parentToChildMsg: ParentToChildMessageReaderClassic }): Promise => { const updatedDepositTx = { @@ -386,7 +397,7 @@ const updateClassicDepositStatusData = async ({ const status = await parentToChildMsg.status() const isCompletedEthDeposit = - isEthDeposit && + !isRetryableDeposit && status >= ParentToChildMessageStatus.FUNDS_DEPOSITED_ON_CHILD const childTxId = (() => { @@ -592,14 +603,14 @@ export async function fetchTeleporterDepositStatusData({ export const getParentToChildMessageDataFromParentTxHash = async ({ depositTxId, - isEthDeposit, + isRetryableDeposit, parentProvider, childProvider, isClassic // optional: if we already know if tx is classic (eg. through subgraph) then no need to re-check in this fn }: { depositTxId: string parentProvider: Provider - isEthDeposit: boolean + isRetryableDeposit: boolean childProvider: Provider isClassic?: boolean }): Promise<{ @@ -632,8 +643,8 @@ export const getParentToChildMessageDataFromParentTxHash = async ({ const getNitroDepositMessage = async () => { // post-nitro handling - if (isEthDeposit) { - // nitro eth deposit + if (!isRetryableDeposit) { + // nitro eth deposit (to the same address) const [ethDepositMessage] = await parentTxReceipt.getEthDeposits( childProvider ) @@ -643,7 +654,7 @@ export const getParentToChildMessageDataFromParentTxHash = async ({ } } - // Else, nitro token deposit + // Else, nitro retryable (token deposit or eth deposit to a custom destination) const [parentToChildMsg] = await parentTxReceipt.getParentToChildMessages( childProvider )