+
Add {nativeCurrency.symbol}
@@ -185,6 +188,7 @@ export function SourceNetworkBox({
maxAmount={maxAmount2}
isMaxAmount={isMaxAmount2}
decimals={nativeCurrency.decimals}
+ aria-label="Amount2 input"
/>
You can transfer {nativeCurrency.symbol} in the same transaction
diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx
index 2052a78420..32e8d9b4fb 100644
--- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx
+++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx
@@ -74,9 +74,11 @@ function MaxButton({
}
function SourceChainTokenBalance({
- balanceOverride
+ balanceOverride,
+ symbolOverride
}: {
balanceOverride?: AmountInputOptions['balance']
+ symbolOverride?: AmountInputOptions['symbol']
}) {
const {
app: { selectedToken }
@@ -105,15 +107,18 @@ function SourceChainTokenBalance({
})
: null
+ const symbol =
+ symbolOverride ?? selectedToken?.symbol ?? nativeCurrency.symbol
+
if (formattedBalance) {
return (
<>
Balance:
{formattedBalance}
@@ -284,7 +289,10 @@ export const TransferPanelMainInput = React.memo(
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 976809afc0..9ef565c058 100644
--- a/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts
+++ b/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts
@@ -1,5 +1,4 @@
import { useAppState } from '../../state'
-import { isExperimentalFeatureEnabled } from '../../util'
import { isTokenNativeUSDC } from '../../util/TokenUtils'
import { useNetworks } from '../useNetworks'
import { useNetworksRelationship } from '../useNetworksRelationship'
@@ -11,9 +10,6 @@ export const useIsBatchTransferSupported = () => {
app: { selectedToken }
} = useAppState()
- if (!isExperimentalFeatureEnabled('batch')) {
- return false
- }
if (!selectedToken) {
return false
}
diff --git a/packages/arb-token-bridge-ui/src/util/TokenDepositUtils.ts b/packages/arb-token-bridge-ui/src/util/TokenDepositUtils.ts
index 39ecfd63b8..3c63a7ec3a 100644
--- a/packages/arb-token-bridge-ui/src/util/TokenDepositUtils.ts
+++ b/packages/arb-token-bridge-ui/src/util/TokenDepositUtils.ts
@@ -13,7 +13,6 @@ 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,
@@ -221,7 +220,6 @@ async function addressIsCustomGatewayToken({
export function isBatchTransfer(tx: MergedTransaction) {
return (
- isExperimentalFeatureEnabled('batch') &&
!tx.isCctp &&
!tx.isWithdrawal &&
tx.assetType === AssetType.ERC20 &&
diff --git a/packages/arb-token-bridge-ui/src/util/index.ts b/packages/arb-token-bridge-ui/src/util/index.ts
index 8392214fa0..4e96205668 100644
--- a/packages/arb-token-bridge-ui/src/util/index.ts
+++ b/packages/arb-token-bridge-ui/src/util/index.ts
@@ -54,7 +54,8 @@ export const getAPIBaseUrl = () => {
return process.env.NODE_ENV === 'test' ? 'http://localhost:3000' : ''
}
-const featureFlags = ['batch'] as const
+// add feature flags to the array
+const featureFlags = [] as const
type FeatureFlag = (typeof featureFlags)[number]
diff --git a/packages/arb-token-bridge-ui/tests/e2e/cypress.d.ts b/packages/arb-token-bridge-ui/tests/e2e/cypress.d.ts
index c3ce4b3cc3..063c7ca76e 100644
--- a/packages/arb-token-bridge-ui/tests/e2e/cypress.d.ts
+++ b/packages/arb-token-bridge-ui/tests/e2e/cypress.d.ts
@@ -8,7 +8,9 @@ import {
searchAndSelectToken,
fillCustomDestinationAddress,
typeAmount,
+ typeAmount2,
findAmountInput,
+ findAmount2Input,
findSourceChainButton,
findDestinationChainButton,
findGasFeeSummary,
@@ -53,7 +55,9 @@ declare global {
}): typeof searchAndSelectToken
fillCustomDestinationAddress(): typeof fillCustomDestinationAddress
typeAmount: typeof typeAmount
+ typeAmount2: typeof typeAmount2
findAmountInput: typeof findAmountInput
+ findAmount2Input: typeof findAmount2Input
findSourceChainButton: typeof findSourceChainButton
findDestinationChainButton: typeof findDestinationChainButton
findGasFeeForChain: typeof findGasFeeForChain
diff --git a/packages/arb-token-bridge-ui/tests/e2e/specfiles.json b/packages/arb-token-bridge-ui/tests/e2e/specfiles.json
index 7ca5456726..8e740d8962 100644
--- a/packages/arb-token-bridge-ui/tests/e2e/specfiles.json
+++ b/packages/arb-token-bridge-ui/tests/e2e/specfiles.json
@@ -24,6 +24,11 @@
"file": "tests/e2e/specs/**/withdrawERC20.cy.{js,jsx,ts,tsx}",
"recordVideo": "false"
},
+ {
+ "name": "Batch deposit",
+ "file": "tests/e2e/specs/**/batchDeposit.cy.{js,jsx,ts,tsx}",
+ "recordVideo": "false"
+ },
{
"name": "TX history",
"file": "tests/e2e/specs/**/txHistory.cy.{js,jsx,ts,tsx}",
diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts
new file mode 100644
index 0000000000..08f768bc31
--- /dev/null
+++ b/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts
@@ -0,0 +1,273 @@
+import {
+ ERC20TokenSymbol,
+ getInitialERC20Balance,
+ getInitialETHBalance,
+ getL1NetworkConfig,
+ getL1NetworkName,
+ getL2NetworkConfig,
+ getL2NetworkName,
+ zeroToLessThanOneETH
+} from '../../support/common'
+import { formatAmount } from '../../../src/util/NumberUtils'
+
+describe('Batch Deposit', () => {
+ let parentNativeTokenBalance,
+ parentErc20Balance,
+ childNativeTokenBalance,
+ childErc20Balance: string
+
+ beforeEach(() => {
+ getInitialERC20Balance({
+ tokenAddress: Cypress.env('ERC20_TOKEN_ADDRESS_CHILD_CHAIN'),
+ multiCallerAddress: getL2NetworkConfig().multiCall,
+ address: Cypress.env('ADDRESS'),
+ rpcURL: Cypress.env('ARB_RPC_URL')
+ }).then(val => (childErc20Balance = formatAmount(val)))
+
+ getInitialETHBalance(
+ Cypress.env('ARB_RPC_URL'),
+ Cypress.env('ADDRESS')
+ ).then(val => (childNativeTokenBalance = formatAmount(val)))
+
+ getInitialETHBalance(
+ Cypress.env('ETH_RPC_URL'),
+ Cypress.env('ADDRESS')
+ ).then(val => (parentNativeTokenBalance = formatAmount(val)))
+
+ getInitialERC20Balance({
+ tokenAddress: Cypress.env('ERC20_TOKEN_ADDRESS_PARENT_CHAIN'),
+ multiCallerAddress: getL1NetworkConfig().multiCall,
+ address: Cypress.env('ADDRESS'),
+ rpcURL: Cypress.env('ETH_RPC_URL')
+ }).then(val => (parentErc20Balance = formatAmount(val)))
+ })
+
+ it('should show L1 and L2 chains, and ETH correctly', () => {
+ cy.login({
+ networkType: 'parentChain',
+ url: '/'
+ })
+ cy.findSourceChainButton(getL1NetworkName())
+ cy.findDestinationChainButton(getL2NetworkName())
+ cy.findSelectTokenButton('ETH')
+ })
+
+ it('should deposit erc-20 and native currency to the same address', () => {
+ // randomize the amount to be sure that previous transactions are not checked in e2e
+ const ERC20AmountToSend = Number((Math.random() * 0.001).toFixed(5))
+ const nativeCurrencyAmountToSend = 0.002
+
+ const isOrbitTest = Cypress.env('ORBIT_TEST') == '1'
+ const depositTime = isOrbitTest ? 'Less than a minute' : '9 minutes'
+
+ cy.login({ networkType: 'parentChain' })
+ context('should add a new token', () => {
+ cy.searchAndSelectToken({
+ tokenName: ERC20TokenSymbol,
+ tokenAddress: Cypress.env('ERC20_TOKEN_ADDRESS_PARENT_CHAIN')
+ })
+ })
+
+ context('should show erc-20 parent balance correctly', () => {
+ cy.findByLabelText(`${ERC20TokenSymbol} balance amount on parentChain`)
+ .should('be.visible')
+ .contains(parentErc20Balance)
+ })
+
+ context('should show erc-20 child balance correctly', () => {
+ cy.findByLabelText(`${ERC20TokenSymbol} balance amount on childChain`)
+ .should('be.visible')
+ .contains(childErc20Balance)
+ })
+
+ context('native currency balance on child chain should not exist', () => {
+ cy.findByLabelText(`ETH balance amount on childChain`).should('not.exist')
+ })
+
+ context('amount2 input should not exist', () => {
+ cy.findAmount2Input().should('not.exist')
+ })
+
+ context('should click add native currency button', () => {
+ cy.findByLabelText('Add native currency button')
+ .should('be.visible')
+ .click()
+ })
+
+ context('amount2 input should show', () => {
+ cy.findAmount2Input().should('be.visible').should('have.value', '')
+ })
+
+ context('native currency balance on child chain should show', () => {
+ cy.findByLabelText(`ETH balance amount on childChain`)
+ .should('be.visible')
+ .contains(childNativeTokenBalance)
+ })
+
+ context('move funds button should be disabled', () => {
+ cy.findMoveFundsButton().should('be.disabled')
+ })
+
+ context('should show gas estimations and summary', () => {
+ cy.typeAmount(ERC20AmountToSend)
+ cy.typeAmount2(nativeCurrencyAmountToSend)
+ cy.findGasFeeSummary(zeroToLessThanOneETH)
+ cy.findGasFeeForChain(getL1NetworkName(), zeroToLessThanOneETH)
+ cy.findGasFeeForChain(getL2NetworkName(), zeroToLessThanOneETH)
+ })
+
+ const txData = {
+ symbol: ERC20TokenSymbol,
+ symbol2: 'ETH',
+ amount: ERC20AmountToSend,
+ amount2: nativeCurrencyAmountToSend
+ }
+
+ context('should deposit successfully', () => {
+ cy.findMoveFundsButton().click()
+ cy.confirmMetamaskTransaction()
+ cy.findTransactionInTransactionHistory({
+ ...txData,
+ duration: depositTime
+ })
+ })
+
+ context('deposit should complete successfully', () => {
+ cy.selectTransactionsPanelTab('settled')
+
+ cy.waitUntil(() => cy.findTransactionInTransactionHistory(txData), {
+ errorMsg: 'Could not find settled ERC20 Batch Deposit transaction',
+ timeout: 60_000,
+ interval: 500
+ })
+
+ cy.findTransactionInTransactionHistory({
+ duration: 'a few seconds ago',
+ ...txData
+ })
+ cy.closeTransactionHistoryPanel()
+ })
+
+ context('funds should reach destination account successfully', () => {
+ // should have more funds on destination chain
+ cy.findByLabelText(`${ERC20TokenSymbol} balance amount on childChain`)
+ .invoke('text')
+ .then(parseFloat)
+ .should('be.gt', Number(parentErc20Balance))
+ cy.findByLabelText(`ETH balance amount on childChain`)
+ .invoke('text')
+ .then(parseFloat)
+ .should(
+ 'be.gt',
+ Number(parentNativeTokenBalance) + nativeCurrencyAmountToSend
+ )
+
+ // the balance on the source chain should not be the same as before
+ cy.findByLabelText(`${ERC20TokenSymbol} balance amount on parentChain`)
+ .invoke('text')
+ .then(parseFloat)
+ .should('be.lt', Number(parentErc20Balance))
+ })
+
+ context('transfer panel amount should be reset', () => {
+ cy.findAmountInput().should('have.value', '')
+ cy.findAmount2Input().should('have.value', '')
+ cy.findMoveFundsButton().should('be.disabled')
+ })
+ })
+
+ it('should deposit erc-20 and native currency to a different address', () => {
+ // randomize the amount to be sure that previous transactions are not checked in e2e
+ const ERC20AmountToSend = Number((Math.random() * 0.001).toFixed(5))
+ const nativeCurrencyAmountToSend = 0.002
+
+ const isOrbitTest = Cypress.env('ORBIT_TEST') == '1'
+ const depositTime = isOrbitTest ? 'Less than a minute' : '9 minutes'
+
+ cy.login({ networkType: 'parentChain' })
+ context('should add a new token', () => {
+ cy.searchAndSelectToken({
+ tokenName: ERC20TokenSymbol,
+ tokenAddress: Cypress.env('ERC20_TOKEN_ADDRESS_PARENT_CHAIN')
+ })
+ })
+
+ context('should fill custom destination address successfully', () => {
+ cy.fillCustomDestinationAddress()
+ })
+
+ context('amount2 input should not exist', () => {
+ cy.findAmount2Input().should('not.exist')
+ })
+
+ context('should click add native currency button', () => {
+ cy.findByLabelText('Add native currency button')
+ .should('be.visible')
+ .click()
+ })
+
+ context('amount2 input should show', () => {
+ cy.findAmount2Input().should('be.visible').should('have.value', '')
+ })
+
+ context('move funds button should be disabled', () => {
+ cy.findMoveFundsButton().should('be.disabled')
+ })
+
+ context('should show gas estimations and summary', () => {
+ cy.typeAmount(ERC20AmountToSend)
+ cy.typeAmount2(nativeCurrencyAmountToSend)
+ cy.findGasFeeSummary(zeroToLessThanOneETH)
+ cy.findGasFeeForChain(getL1NetworkName(), zeroToLessThanOneETH)
+ cy.findGasFeeForChain(getL2NetworkName(), zeroToLessThanOneETH)
+ })
+
+ const txData = {
+ symbol: ERC20TokenSymbol,
+ symbol2: 'ETH',
+ amount: ERC20AmountToSend,
+ amount2: nativeCurrencyAmountToSend
+ }
+
+ context('should deposit successfully', () => {
+ cy.findMoveFundsButton().click()
+ cy.confirmMetamaskTransaction()
+ cy.findTransactionInTransactionHistory({
+ ...txData,
+ duration: depositTime
+ })
+ cy.openTransactionDetails(txData)
+ cy.findTransactionDetailsCustomDestinationAddress(
+ Cypress.env('CUSTOM_DESTINATION_ADDRESS')
+ )
+ cy.closeTransactionDetails()
+ })
+
+ context('deposit should complete successfully', () => {
+ cy.selectTransactionsPanelTab('settled')
+
+ cy.waitUntil(() => cy.findTransactionInTransactionHistory(txData), {
+ errorMsg: 'Could not find settled ERC20 Batch Deposit transaction',
+ timeout: 60_000,
+ interval: 500
+ })
+
+ cy.findTransactionInTransactionHistory({
+ duration: 'a few seconds ago',
+ ...txData
+ })
+ cy.openTransactionDetails(txData)
+ cy.findTransactionDetailsCustomDestinationAddress(
+ Cypress.env('CUSTOM_DESTINATION_ADDRESS')
+ )
+ cy.closeTransactionDetails()
+ cy.closeTransactionHistoryPanel()
+ })
+
+ context('transfer panel amount should be reset', () => {
+ cy.findAmountInput().should('have.value', '')
+ cy.findAmount2Input().should('have.value', '')
+ cy.findMoveFundsButton().should('be.disabled')
+ })
+ })
+})
diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts
index 9a6a2ed801..2568bfbef9 100644
--- a/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts
+++ b/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts
@@ -7,13 +7,12 @@ import {
getInitialERC20Balance,
getL1NetworkConfig,
zeroToLessThanOneETH,
+ moreThanZeroBalance,
getL1NetworkName,
getL2NetworkName,
ERC20TokenSymbol
} from '../../support/common'
-const moreThanZeroBalance = /0(\.\d+)/
-
const depositTestCases = {
'Standard ERC20': {
symbol: ERC20TokenSymbol,
@@ -73,7 +72,6 @@ describe('Deposit Token', () => {
cy.findByLabelText(`${testCase.symbol} balance amount on parentChain`)
.should('be.visible')
.contains(l1ERC20bal)
- .should('be.visible')
})
context('should show gas estimations', () => {
diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/urlQueryParam.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/urlQueryParam.cy.ts
index 4b19a7d3f5..7aff7357bc 100644
--- a/packages/arb-token-bridge-ui/tests/e2e/specs/urlQueryParam.cy.ts
+++ b/packages/arb-token-bridge-ui/tests/e2e/specs/urlQueryParam.cy.ts
@@ -251,3 +251,5 @@ describe('User enters site with query params on URL', () => {
)
})
})
+
+// TODO: Test amount2 when query params e2e is added back
diff --git a/packages/arb-token-bridge-ui/tests/support/commands.ts b/packages/arb-token-bridge-ui/tests/support/commands.ts
index b636e7ac86..53eeff9028 100644
--- a/packages/arb-token-bridge-ui/tests/support/commands.ts
+++ b/packages/arb-token-bridge-ui/tests/support/commands.ts
@@ -176,10 +176,20 @@ export function findAmountInput(): Cypress.Chainable
> {
return cy.findByLabelText('Amount input')
}
+export function findAmount2Input(): Cypress.Chainable> {
+ return cy.findByLabelText('Amount2 input')
+}
+
export function typeAmount(
amount: string | number
): Cypress.Chainable> {
- return cy.findAmountInput().type(String(amount))
+ return cy.findAmountInput().scrollIntoView().type(String(amount))
+}
+
+export function typeAmount2(
+ amount: string | number
+): Cypress.Chainable> {
+ return cy.findAmount2Input().scrollIntoView().type(String(amount))
}
export function findSourceChainButton(
@@ -250,12 +260,21 @@ export function closeTransactionHistoryPanel() {
export function openTransactionDetails({
amount,
- symbol
+ amount2,
+ symbol,
+ symbol2
}: {
amount: number
+ amount2?: number
symbol: string
+ symbol2: string
}): Cypress.Chainable> {
- cy.findTransactionInTransactionHistory({ amount, symbol }).within(() => {
+ cy.findTransactionInTransactionHistory({
+ amount,
+ amount2,
+ symbol,
+ symbol2
+ }).within(() => {
cy.findByLabelText('Transaction details button').click()
})
return cy.findByText('Transaction details').should('be.visible')
@@ -278,18 +297,26 @@ export function findTransactionDetailsCustomDestinationAddress(
export function findTransactionInTransactionHistory({
symbol,
+ symbol2,
amount,
+ amount2,
duration
}: {
symbol: string
+ symbol2?: string
amount: number
+ amount2?: number
duration?: string
}) {
// Replace . with \.
const parsedAmount = amount.toString().replace(/\./g, '\\.')
+
const rowId = new RegExp(
- `(claimable|deposit)-row-[0-9xabcdef]*-${parsedAmount}${symbol}`
+ `(claimable|deposit)-row-[0-9xabcdef]*-${parsedAmount}${symbol}${
+ amount2 && symbol2 ? `-${amount2}${symbol2}` : ''
+ }`
)
+
cy.findByTestId(rowId).as('row')
if (duration) {
cy.get('@row').findAllByText(duration).first().should('be.visible')
@@ -336,7 +363,9 @@ Cypress.Commands.addAll({
searchAndSelectToken,
fillCustomDestinationAddress,
typeAmount,
+ typeAmount2,
findAmountInput,
+ findAmount2Input,
findSourceChainButton,
findDestinationChainButton,
findGasFeeForChain,
diff --git a/packages/arb-token-bridge-ui/tests/support/common.ts b/packages/arb-token-bridge-ui/tests/support/common.ts
index b1cc0011b1..575bba6e4d 100644
--- a/packages/arb-token-bridge-ui/tests/support/common.ts
+++ b/packages/arb-token-bridge-ui/tests/support/common.ts
@@ -95,6 +95,7 @@ export const ERC20TokenDecimals = 18
export const invalidTokenAddress = '0x0000000000000000000000000000000000000000'
export const zeroToLessThanOneETH = /0(\.\d+)*( ETH)/
+export const moreThanZeroBalance = /0(\.\d+)/
export const importTokenThroughUI = (address: string) => {
// Click on the ETH dropdown (Select token button)