diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx index 114fa3950e..1c92ce2bfa 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx @@ -13,6 +13,7 @@ import { PlusCircleIcon } from '@heroicons/react/24/outline' import dayjs from 'dayjs' +import { getProviderForChainId } from '@/token-bridge-sdk/utils' import { getStandardizedDate, @@ -30,10 +31,20 @@ import { PendingDepositWarning } from './PendingDepositWarning' import { TransactionsTableRow } from './TransactionsTableRow' import { EmptyTransactionHistory } from './EmptyTransactionHistory' import { Address } from '../../util/AddressUtils' +import { MergedTransaction } from '../../state/app/state' +import { useNativeCurrency } from '../../hooks/useNativeCurrency' + +export const BatchTransferNativeTokenTooltip = ({ + children, + tx +}: PropsWithChildren<{ tx: MergedTransaction }>) => { + const childProvider = getProviderForChainId(tx.childChainId) + const nativeCurrency = useNativeCurrency({ provider: childProvider }) -export const BatchTransferEthTooltip = ({ children }: PropsWithChildren) => { return ( - + {children} ) 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 755a7a1475..d06b766e5f 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetails.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetails.tsx @@ -7,6 +7,7 @@ import dayjs from 'dayjs' import CctpLogoColor from '@/images/CctpLogoColor.svg' import ArbitrumLogo from '@/images/ArbitrumLogo.svg' import EthereumLogoRoundLight from '@/images/EthereumLogoRoundLight.svg' +import { getProviderForChainId } from '@/token-bridge-sdk/utils' import { useTxDetailsStore } from './TransactionHistory' import { getExplorerUrl, getNetworkName, isNetwork } from '../../util/networks' @@ -24,7 +25,8 @@ import { isTxCompleted } from './helpers' import { Address } from '../../util/AddressUtils' import { sanitizeTokenSymbol } from '../../util/TokenUtils' import { isBatchTransfer } from '../../util/TokenDepositUtils' -import { BatchTransferEthTooltip } from './TransactionHistoryTable' +import { BatchTransferNativeTokenTooltip } from './TransactionHistoryTable' +import { useNativeCurrency } from '../../hooks/useNativeCurrency' const DetailsBox = ({ children, @@ -63,7 +65,10 @@ export const TransactionsTableDetails = ({ ) }, [transactions, txFromStore]) - if (!tx || !address) { + const childProvider = getProviderForChainId(tx?.childChainId ?? 0) + const nativeCurrency = useNativeCurrency({ provider: childProvider }) + + if (!tx || !address || !nativeCurrency) { return null } @@ -153,17 +158,19 @@ export const TransactionsTableDetails = ({ )} {isBatchTransfer(tx) && ( - +
ETH logo {formatAmount(Number(tx.value2), { - symbol: ether.symbol + symbol: nativeCurrency.symbol })} {isNetwork(tx.sourceChainId).isEthereumMainnet && ( @@ -172,7 +179,7 @@ export const TransactionsTableDetails = ({ )}
-
+ )} diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRow.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRow.tsx index a153c0682c..f7bbc970df 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRow.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRow.tsx @@ -9,6 +9,7 @@ import { } from '@heroicons/react/24/outline' import EthereumLogoRoundLight from '@/images/EthereumLogoRoundLight.svg' import Image from 'next/image' +import { getProviderForChainId } from '@/token-bridge-sdk/utils' import { DepositStatus, MergedTransaction } from '../../state/app/state' import { formatAmount } from '../../util/NumberUtils' @@ -30,9 +31,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 { BatchTransferEthTooltip } from './TransactionHistoryTable' +import { BatchTransferNativeTokenTooltip } from './TransactionHistoryTable' +import { useNativeCurrency } from '../../hooks/useNativeCurrency' const StatusLabel = ({ tx }: { tx: MergedTransaction }) => { const { sourceChainId, destinationChainId } = tx @@ -128,6 +129,8 @@ export function TransactionsTableRow({ className?: string }) { const { open: openTxDetails } = useTxDetailsStore() + const childProvider = getProviderForChainId(tx.childChainId) + const nativeCurrency = useNativeCurrency({ provider: childProvider }) const { sourceChainId, destinationChainId } = tx @@ -190,21 +193,21 @@ export function TransactionsTableRow({ {isBatchTransfer(tx) && ( - +
ETH logo {formatAmount(Number(tx.value2), { - symbol: ether.symbol + symbol: nativeCurrency.symbol })}
-
+ )}
diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/CustomFeeTokenApprovalDialog.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/CustomFeeTokenApprovalDialog.tsx index dd3332e9f5..ab1b9cfa8f 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/CustomFeeTokenApprovalDialog.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/CustomFeeTokenApprovalDialog.tsx @@ -16,6 +16,8 @@ import { useNetworksRelationship } from '../../hooks/useNetworksRelationship' import { shortenAddress } from '../../util/CommonUtils' import { NoteBox } from '../common/NoteBox' import { BridgeTransferStarterFactory } from '@/token-bridge-sdk/BridgeTransferStarterFactory' +import { useIsBatchTransferSupported } from '../../hooks/TransferPanel/useIsBatchTransferSupported' +import { useArbQueryParams } from '../../hooks/useArbQueryParams' export type CustomFeeTokenApprovalDialogProps = UseDialogProps & { customFeeToken: NativeCurrencyErc20 @@ -34,6 +36,10 @@ export function CustomFeeTokenApprovalDialog( const { sourceChain, destinationChain } = networks const { parentChain, parentChainProvider } = useNetworksRelationship(networks) const { isEthereumMainnet } = isNetwork(parentChain.id) + const isBatchTransferSupported = useIsBatchTransferSupported() + const [{ amount2 }] = useArbQueryParams() + + const isBatchTransfer = isBatchTransferSupported && Number(amount2) > 0 const { data: l1Signer } = useSigner({ chainId: parentChain.id }) const l1GasPrice = useGasPrice({ provider: parentChainProvider }) @@ -141,6 +147,16 @@ export function CustomFeeTokenApprovalDialog( as the fee token. Before continuing with your deposit, you must first allow the bridge contract to access your{' '} {customFeeToken.symbol}. + {isBatchTransfer && ( + + {' '} + This includes your deposit of{' '} + + {amount2} {customFeeToken.symbol} + {' '} + and gas fees. + + )} 0 + const isNativeCurrencyApprovalRequired = await bridgeTransferStarter.requiresNativeCurrencyApproval({ signer, - amount: amountBigNumber + amount: amountBigNumber, + options: { + approvalAmountIncrease: isCustomNativeTokenAmount2 + ? utils.parseUnits(amount2, nativeCurrency.decimals) + : undefined + } }) if (isNativeCurrencyApprovalRequired) { @@ -770,7 +780,12 @@ export function TransferPanel() { const approvalTx = await bridgeTransferStarter.approveNativeCurrency({ signer, - amount: amountBigNumber + amount: amountBigNumber, + options: { + approvalAmountIncrease: isCustomNativeTokenAmount2 + ? utils.parseUnits(amount2, nativeCurrency.decimals) + : undefined + } }) if (approvalTx) { diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/DestinationNetworkBox.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/DestinationNetworkBox.tsx index e20a722a0c..c5a2ee8078 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/DestinationNetworkBox.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/DestinationNetworkBox.tsx @@ -32,8 +32,22 @@ import { useArbQueryParams } from '../../../hooks/useArbQueryParams' function NativeCurrencyDestinationBalance({ prefix }: { prefix?: string }) { const nativeCurrencyBalances = useNativeCurrencyBalances() const [networks] = useNetworks() + const nativeCurrency = useNativeCurrency({ + provider: networks.destinationChainProvider + }) const { isDepositMode } = useNetworksRelationship(networks) + if (nativeCurrency.isCustom) { + return ( + + ) + } + return (

- You can transfer ETH in the same transaction if you wish to. + You can transfer {nativeCurrency.symbol} in the same transaction + if you wish to.

)} diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts index f7d6d6b4ce..71c626c84f 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts @@ -105,12 +105,9 @@ export function useMaxAmount() { if (!isDepositMode) { return undefined } - if (nativeCurrency.isCustom) { - return undefined - } return nativeCurrencyMaxAmount - }, [isDepositMode, nativeCurrency.isCustom, nativeCurrencyMaxAmount]) + }, [isDepositMode, nativeCurrencyMaxAmount]) return { maxAmount, diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelSummary.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelSummary.tsx index 2a2d6a2b33..cca1569c09 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelSummary.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelSummary.tsx @@ -251,7 +251,10 @@ export function TransferPanelSummary({ token }: TransferPanelSummaryProps) { )} {isBatchTransferSupported && Number(amount2) > 0 && ( - + {amount2} ETH + + {' '} + and {amount2} {childChainNativeCurrency.symbol} + )}
diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/useTransferReadiness.ts b/packages/arb-token-bridge-ui/src/components/TransferPanel/useTransferReadiness.ts index 46dc6768c7..ad61553ad2 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/useTransferReadiness.ts +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/useTransferReadiness.ts @@ -236,18 +236,19 @@ export function useTransferReadiness(): UseTransferReadinessResult { return notReady() } - const sendsAdditionalEth = Number(amount2) > 0 - const notEnoughEthForAdditionalEthTransfer = - Number(amount2) > - ethBalanceFloat - (estimatedL1GasFees + estimatedL2GasFees) + const sendsAmount2 = Number(amount2) > 0 + const notEnoughAmount2 = nativeCurrency.isCustom + ? Number(amount2) > Number(customFeeTokenL1BalanceFloat) + : Number(amount2) > + ethBalanceFloat - (estimatedL1GasFees + estimatedL2GasFees) if (isNaN(Number(amount)) || Number(amount) === 0) { return notReady({ errorMessages: { inputAmount2: - sendsAdditionalEth && notEnoughEthForAdditionalEthTransfer + sendsAmount2 && notEnoughAmount2 ? getInsufficientFundsErrorMessage({ - asset: ether.symbol, + asset: nativeCurrency.symbol, chain: networks.sourceChain.name }) : undefined @@ -337,9 +338,9 @@ export function useTransferReadiness(): UseTransferReadinessResult { chain: networks.sourceChain.name }), inputAmount2: - sendsAdditionalEth && notEnoughEthForAdditionalEthTransfer + sendsAmount2 && notEnoughAmount2 ? getInsufficientFundsErrorMessage({ - asset: ether.symbol, + asset: nativeCurrency.symbol, chain: networks.sourceChain.name }) : undefined @@ -355,13 +356,26 @@ export function useTransferReadiness(): UseTransferReadinessResult { } // Check amount against custom fee token balance - if (Number(amount) > customFeeTokenBalanceFloat) { + if ( + Number(amount) > customFeeTokenBalanceFloat || + (sendsAmount2 && notEnoughAmount2) + ) { return notReady({ errorMessages: { - inputAmount1: getInsufficientFundsErrorMessage({ - asset: nativeCurrency.symbol, - chain: networks.sourceChain.name - }) + inputAmount1: + Number(amount) > customFeeTokenBalanceFloat + ? getInsufficientFundsErrorMessage({ + asset: nativeCurrency.symbol, + chain: networks.sourceChain.name + }) + : undefined, + inputAmount2: + sendsAmount2 && notEnoughAmount2 + ? getInsufficientFundsErrorMessage({ + asset: nativeCurrency.symbol, + chain: networks.sourceChain.name + }) + : undefined } }) } @@ -409,29 +423,55 @@ export function useTransferReadiness(): UseTransferReadinessResult { } // We have to check if there's enough ETH to cover L1 gas - if (estimatedL1GasFees > ethBalanceFloat) { + if ( + estimatedL1GasFees > ethBalanceFloat || + (sendsAmount2 && notEnoughAmount2) + ) { return notReady({ errorMessages: { - inputAmount1: getInsufficientFundsForGasFeesErrorMessage({ - asset: ether.symbol, - chain: networks.sourceChain.name, - balance: formatAmount(ethBalanceFloat), - requiredBalance: formatAmount(estimatedL1GasFees) - }) + inputAmount1: + estimatedL1GasFees > ethBalanceFloat + ? getInsufficientFundsForGasFeesErrorMessage({ + asset: ether.symbol, + chain: networks.sourceChain.name, + balance: formatAmount(ethBalanceFloat), + requiredBalance: formatAmount(estimatedL1GasFees) + }) + : undefined, + inputAmount2: + sendsAmount2 && notEnoughAmount2 + ? getInsufficientFundsErrorMessage({ + asset: nativeCurrency.symbol, + chain: networks.sourceChain.name + }) + : undefined } }) } // We have to check if there's enough of the custom fee token to cover L2 gas - if (estimatedL2GasFees > customFeeTokenL1BalanceFloat) { + if ( + estimatedL2GasFees > customFeeTokenL1BalanceFloat || + (sendsAmount2 && notEnoughAmount2) + ) { return notReady({ errorMessages: { - inputAmount1: getInsufficientFundsForGasFeesErrorMessage({ - asset: nativeCurrency.symbol, - chain: networks.sourceChain.name, - balance: formatAmount(customFeeTokenL1BalanceFloat), - requiredBalance: formatAmount(estimatedL2GasFees) - }) + inputAmount1: + estimatedL2GasFees > customFeeTokenL1BalanceFloat + ? getInsufficientFundsForGasFeesErrorMessage({ + asset: nativeCurrency.symbol, + chain: networks.sourceChain.name, + balance: formatAmount(customFeeTokenL1BalanceFloat), + requiredBalance: formatAmount(estimatedL2GasFees) + }) + : undefined, + inputAmount2: + sendsAmount2 && notEnoughAmount2 + ? getInsufficientFundsErrorMessage({ + asset: nativeCurrency.symbol, + chain: networks.sourceChain.name + }) + : undefined } }) } @@ -443,10 +483,7 @@ export function useTransferReadiness(): UseTransferReadinessResult { const notEnoughEthForGasFees = estimatedL1GasFees + estimatedL2GasFees > ethBalanceFloat - if ( - notEnoughEthForGasFees || - (sendsAdditionalEth && notEnoughEthForAdditionalEthTransfer) - ) { + if (notEnoughEthForGasFees || (sendsAmount2 && notEnoughAmount2)) { return notReady({ errorMessages: { inputAmount1: notEnoughEthForGasFees @@ -460,9 +497,9 @@ export function useTransferReadiness(): UseTransferReadinessResult { }) : undefined, inputAmount2: - sendsAdditionalEth && notEnoughEthForAdditionalEthTransfer + sendsAmount2 && notEnoughAmount2 ? getInsufficientFundsErrorMessage({ - asset: ether.symbol, + asset: nativeCurrency.symbol, chain: networks.sourceChain.name }) : undefined diff --git a/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts b/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts index f1e006a0cf..976809afc0 100644 --- a/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts +++ b/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts @@ -1,18 +1,15 @@ import { useAppState } from '../../state' import { isExperimentalFeatureEnabled } from '../../util' import { isTokenNativeUSDC } from '../../util/TokenUtils' -import { useNativeCurrency } from '../useNativeCurrency' import { useNetworks } from '../useNetworks' import { useNetworksRelationship } from '../useNetworksRelationship' export const useIsBatchTransferSupported = () => { const [networks] = useNetworks() - const { isDepositMode, isTeleportMode, childChainProvider } = - useNetworksRelationship(networks) + const { isDepositMode, isTeleportMode } = useNetworksRelationship(networks) const { app: { selectedToken } } = useAppState() - const nativeCurrency = useNativeCurrency({ provider: childChainProvider }) if (!isExperimentalFeatureEnabled('batch')) { return false @@ -30,10 +27,6 @@ export const useIsBatchTransferSupported = () => { if (isTeleportMode) { return false } - // TODO: disable custom native currency for now, check if this works - if (nativeCurrency.isCustom) { - return false - } return true } diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts index 7dcbafa9ef..0d6a64f28e 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts @@ -63,6 +63,9 @@ export type TransferProps = { export type RequiresNativeCurrencyApprovalProps = { amount: BigNumber signer: Signer + options?: { + approvalAmountIncrease?: BigNumber + } } export type ApproveNativeCurrencyEstimateGasProps = { @@ -73,6 +76,9 @@ export type ApproveNativeCurrencyEstimateGasProps = { export type ApproveNativeCurrencyProps = { signer: Signer amount: BigNumber + options?: { + approvalAmountIncrease?: BigNumber + } } export type RequiresTokenApprovalProps = { diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts index d11dcbdcf3..3934103f21 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts @@ -75,7 +75,8 @@ export class Erc20DepositStarter extends BridgeTransferStarter { public async requiresNativeCurrencyApproval({ amount, - signer + signer, + options }: RequiresNativeCurrencyApprovalProps) { if (!this.sourceChainErc20Address) { throw Error('Erc20 token address not found') @@ -128,7 +129,9 @@ export class Erc20DepositStarter extends BridgeTransferStarter { // We want to bridge a certain amount of an ERC-20 token, but the Retryable fees on the destination chain will be paid in the custom fee token // We have to check if the native-token spending allowance is enough to cover the fees return customFeeTokenAllowanceForSourceChainGateway.lt( - estimatedDestinationChainGasFee + estimatedDestinationChainGasFee.add( + options?.approvalAmountIncrease ?? BigNumber.from(0) + ) ) } @@ -153,7 +156,8 @@ export class Erc20DepositStarter extends BridgeTransferStarter { public async approveNativeCurrency({ signer, - amount + amount, + options }: ApproveNativeCurrencyProps) { if (!this.sourceChainErc20Address) { throw Error('Erc20 token address not found') @@ -198,7 +202,9 @@ export class Erc20DepositStarter extends BridgeTransferStarter { return erc20Bridger.approveGasToken({ erc20ParentAddress: this.sourceChainErc20Address, parentSigner: signer, - amount: estimatedDestinationChainGasFee + amount: estimatedDestinationChainGasFee.add( + options?.approvalAmountIncrease ?? BigNumber.from(0) + ) }) }