diff --git a/locales/base/translation.json b/locales/base/translation.json index 919e22375e4..5df45be589d 100644 --- a/locales/base/translation.json +++ b/locales/base/translation.json @@ -2601,9 +2601,13 @@ "moreInformation": "More information", "estNetworkFee": "Est. Network Fee", "maxNetworkFee": "Max Network Fee", - "networkFeeDescription": "The network fee is required by the network to process the deposit transaction.", - "networkFeeDescriptionWithdrawal": "The network fee is required by the network to process the withdrawal transaction.", - "networkSwapFeeDescription": "The network fee is required by the network to process the deposit transactions. The {{appName}} fee of {{appFeePercentage}}% is charged for your use of our product.", + "estCrossChainFee": "Est. Cross-chain Fee", + "maxCrossChainFee": "Max Cross-chain Fee", + "description_deposit": "The network fee is required by the network to process the deposit transaction.", + "description_withdraw": "The network fee is required by the network to process the withdrawal transaction.", + "description_depositSwapFee": "The network fee is required by the network to process the deposit transactions. The {{appName}} fee of {{appFeePercentage}}% is charged for your use of our product.", + "description_depositCrossChain": "The network fee is required by the network to process the deposit transaction. The cross-chain fee is charged by the cross-chain provider.", + "description_depositCrossChainWithSwapFee": "The network fee is required by the network to process the deposit transactions. The {{appName}} fee of {{appFeePercentage}}% is charged for your use of our product. The cross-chain fee is charged by the cross-chain provider.", "appSwapFee": "{{appName}} Fee" }, "swapBottomSheet": { diff --git a/src/earn/EarnEnterAmount.test.tsx b/src/earn/EarnEnterAmount.test.tsx index 74b25b2fe6e..739d9587c13 100644 --- a/src/earn/EarnEnterAmount.test.tsx +++ b/src/earn/EarnEnterAmount.test.tsx @@ -146,8 +146,8 @@ const mockCrossChainSwapTransaction: SwapTransaction = { ...mockSwapTransaction, swapType: 'cross-chain', estimatedDuration: 300, - maxCrossChainFee: '0.1', - estimatedCrossChainFee: '0.05', + maxCrossChainFee: '1000000000000000', + estimatedCrossChainFee: '500000000000000', sellTokenAddress: mockCeloAddress, price: '4', guaranteedPrice: '4', @@ -876,6 +876,8 @@ describe('EarnEnterAmount', () => { const replaceSeparators = (value: string) => value.replace(/\./g, '|').replace(/,/g, group).replace(/\|/g, decimal) + const defaultFormat = BigNumber.config().FORMAT + beforeEach(() => { jest .mocked(getNumberFormatSettings) @@ -889,6 +891,10 @@ describe('EarnEnterAmount', () => { }) }) + afterEach(() => { + BigNumber.config({ FORMAT: defaultFormat }) + }) + const mockStore = createMockStore({ tokens: { tokenBalances: { @@ -971,7 +977,7 @@ describe('EarnEnterAmount', () => { isPreparingTransactions: false, }) - const { getByTestId, getByText } = render( + const { getByTestId, getByText, queryByTestId } = render( @@ -980,9 +986,16 @@ describe('EarnEnterAmount', () => { fireEvent.changeText(getByTestId('EarnEnterAmount/TokenAmountInput'), '1') fireEvent.press(getByTestId('LabelWithInfo/FeeLabel')) expect(getByText('earnFlow.enterAmount.feeBottomSheet.feeDetails')).toBeVisible() - expect(getByTestId('EstNetworkFee/Value')).toBeTruthy() - expect(getByTestId('MaxNetworkFee/Value')).toBeTruthy() - expect(getByText('earnFlow.enterAmount.feeBottomSheet.networkFeeDescription')).toBeVisible() + expect(getByTestId('EstNetworkFee')).toBeTruthy() + expect(getByTestId('MaxNetworkFee')).toBeTruthy() + expect(queryByTestId('SwapFee')).toBeFalsy() + expect(queryByTestId('EstCrossChainFee')).toBeFalsy() + expect(queryByTestId('MaxCrossChainFee')).toBeFalsy() + expect(getByTestId('EstNetworkFee/Value')).toHaveTextContent('₱0.012 (0.000006 ETH)') + expect(getByTestId('MaxNetworkFee/Value')).toHaveTextContent('₱0.012 (0.000006 ETH)') + expect( + getByText('earnFlow.enterAmount.feeBottomSheet.description, {"context":"deposit"}') + ).toBeVisible() }) it('should show swap fees on the FeeDetailsBottomSheet when swap transaction is present', async () => { @@ -997,6 +1010,43 @@ describe('EarnEnterAmount', () => { isPreparingTransactions: false, }) + const { getByTestId, getByText, queryByTestId } = render( + + + + ) + + fireEvent.changeText(getByTestId('EarnEnterAmount/TokenAmountInput'), '1') + fireEvent.press(getByTestId('LabelWithInfo/FeeLabel')) + expect(getByText('earnFlow.enterAmount.feeBottomSheet.feeDetails')).toBeVisible() + expect(getByTestId('EstNetworkFee')).toBeTruthy() + expect(getByTestId('MaxNetworkFee')).toBeTruthy() + expect(getByTestId('SwapFee')).toBeTruthy() + expect(queryByTestId('EstCrossChainFee')).toBeFalsy() + expect(queryByTestId('MaxCrossChainFee')).toBeFalsy() + expect(getByTestId('EstNetworkFee/Value')).toHaveTextContent('₱0.012 (0.000006 ETH)') + expect(getByTestId('MaxNetworkFee/Value')).toHaveTextContent('₱0.012 (0.000006 ETH)') + expect(getByTestId('SwapFee/Value')).toHaveTextContent('₱0.008 (0.006 USDC)') + expect( + getByText( + 'earnFlow.enterAmount.feeBottomSheet.description, {"context":"depositSwapFee","appFeePercentage":"0.6"}' + ) + ).toBeVisible() + expect(getByTestId('FeeDetailsBottomSheet/GotIt')).toBeVisible() + }) + + it('should show swap and cross chain fees on the FeeDetailsBottomSheet when cross chain swap transaction is present', async () => { + jest.mocked(usePrepareEnterAmountTransactionsCallback).mockReturnValue({ + prepareTransactionsResult: { + prepareTransactionsResult: mockPreparedTransaction, + swapTransaction: mockCrossChainSwapTransaction, + }, + refreshPreparedTransactions: jest.fn(), + clearPreparedTransactions: jest.fn(), + prepareTransactionError: undefined, + isPreparingTransactions: false, + }) + const { getByTestId, getByText } = render( @@ -1006,12 +1056,19 @@ describe('EarnEnterAmount', () => { fireEvent.changeText(getByTestId('EarnEnterAmount/TokenAmountInput'), '1') fireEvent.press(getByTestId('LabelWithInfo/FeeLabel')) expect(getByText('earnFlow.enterAmount.feeBottomSheet.feeDetails')).toBeVisible() - expect(getByTestId('EstNetworkFee/Value')).toBeTruthy() - expect(getByTestId('MaxNetworkFee/Value')).toBeTruthy() - expect(getByTestId('SwapFee/Value')).toBeTruthy() + expect(getByTestId('EstNetworkFee')).toBeTruthy() + expect(getByTestId('MaxNetworkFee')).toBeTruthy() + expect(getByTestId('SwapFee')).toBeTruthy() + expect(getByTestId('EstCrossChainFee')).toBeTruthy() + expect(getByTestId('MaxCrossChainFee')).toBeTruthy() + expect(getByTestId('EstNetworkFee/Value')).toHaveTextContent('₱0.012 (0.000006 ETH)') + expect(getByTestId('MaxNetworkFee/Value')).toHaveTextContent('₱0.012 (0.000006 ETH)') + expect(getByTestId('SwapFee/Value')).toHaveTextContent('₱0.008 (0.006 USDC)') + expect(getByTestId('EstCrossChainFee/Value')).toHaveTextContent('₱1.00 (0.0005 ETH)') + expect(getByTestId('MaxCrossChainFee/Value')).toHaveTextContent('₱2.00 (0.001 ETH)') expect( getByText( - 'earnFlow.enterAmount.feeBottomSheet.networkSwapFeeDescription, {"appFeePercentage":"0.6"}' + 'earnFlow.enterAmount.feeBottomSheet.description, {"context":"depositCrossChainWithSwapFee","appFeePercentage":"0.6"}' ) ).toBeVisible() expect(getByTestId('FeeDetailsBottomSheet/GotIt')).toBeVisible() diff --git a/src/earn/EarnEnterAmount.tsx b/src/earn/EarnEnterAmount.tsx index 86d32baf7e0..e0f195ae8eb 100644 --- a/src/earn/EarnEnterAmount.tsx +++ b/src/earn/EarnEnterAmount.tsx @@ -42,7 +42,8 @@ import { StatsigFeatureGates } from 'src/statsig/types' import Colors from 'src/styles/colors' import { typeScale } from 'src/styles/fonts' import { Spacing } from 'src/styles/styles' -import { SwapTransaction } from 'src/swap/types' +import getCrossChainFee from 'src/swap/getCrossChainFee' +import { SwapFeeAmount, SwapTransaction } from 'src/swap/types' import { useSwappableTokens, useTokenInfo } from 'src/tokens/hooks' import { feeCurrenciesSelector } from 'src/tokens/selectors' import { TokenBalance } from 'src/tokens/slice' @@ -226,6 +227,21 @@ export default function EarnEnterAmount({ route }: Props) { }) } + const crossChainFeeCurrency = useSelector((state) => + feeCurrenciesSelector(state, inputToken.networkId) + ).find((token) => token.isNative) + const crossChainFee = + swapTransaction?.swapType === 'cross-chain' && prepareTransactionsResult + ? getCrossChainFee({ + feeCurrency: crossChainFeeCurrency, + preparedTransactions: prepareTransactionsResult, + estimatedCrossChainFee: swapTransaction.estimatedCrossChainFee, + maxCrossChainFee: swapTransaction.maxCrossChainFee, + fromTokenId: inputToken.tokenId, + sellAmount: swapTransaction.sellAmount, + }) + : undefined + // This is for withdrawals as we want the user to be able to input the amounts in the deposit token const { transactionToken, transactionTokenAmount } = useMemo(() => { const transactionToken = isWithdrawal ? withdrawToken : inputToken @@ -500,6 +516,7 @@ export default function EarnEnterAmount({ route }: Props) { token={inputToken} tokenAmount={processedAmounts.token.bignum} isWithdrawal={isWithdrawal} + crossChainFee={crossChainFee} /> )} {swapTransaction && processedAmounts.token.bignum && ( @@ -745,7 +762,7 @@ function TransactionDepositDetails({ ) } -// Might be sharable with src/swap/FeeInfoBottomSheet.tsx +// TODO(ACT-1534) src/swap/FeeInfoBottomSheet.tsx function FeeDetailsBottomSheet({ forwardedRef, testID, @@ -757,6 +774,7 @@ function FeeDetailsBottomSheet({ token, tokenAmount, isWithdrawal, + crossChainFee, }: { forwardedRef: React.RefObject testID: string @@ -768,6 +786,7 @@ function FeeDetailsBottomSheet({ token: TokenBalance tokenAmount: BigNumber isWithdrawal: boolean + crossChainFee?: SwapFeeAmount }) { const { t } = useTranslation() const inputToken = useTokenInfo(pool.dataProps.depositTokenId) @@ -858,23 +877,71 @@ function FeeDetailsBottomSheet({ )} + {crossChainFee && crossChainFee.token && ( + <> + + + + + {t('earnFlow.enterAmount.feeBottomSheet.estCrossChainFee')} + + + {'≈ '} + + {' ('} + + {')'} + + + {crossChainFee.maxAmount && ( + + + {t('earnFlow.enterAmount.feeBottomSheet.maxCrossChainFee')} + + + {'≈ '} + + {' ('} + + {')'} + + + )} + + + )} {t('earnFlow.enterAmount.feeBottomSheet.moreInformation')} - {swapFeeAmount ? ( - - {t('earnFlow.enterAmount.feeBottomSheet.networkSwapFeeDescription', { - appFeePercentage: swapTransaction?.appFeePercentageIncludedInPrice, - })} - - ) : ( - - {isWithdrawal - ? t('earnFlow.enterAmount.feeBottomSheet.networkFeeDescriptionWithdrawal') - : t('earnFlow.enterAmount.feeBottomSheet.networkFeeDescription')} - - )} + + {t('earnFlow.enterAmount.feeBottomSheet.description', { + context: isWithdrawal + ? 'withdraw' + : swapFeeAmount + ? crossChainFee + ? 'depositCrossChainWithSwapFee' + : 'depositSwapFee' + : crossChainFee + ? 'depositCrossChain' + : 'deposit', + appFeePercentage: swapTransaction?.appFeePercentageIncludedInPrice, + })} +