Skip to content

Commit

Permalink
feat(earn): cross chain swap and deposit changes on deposit entrypoint (
Browse files Browse the repository at this point in the history
#6390)

### Description


[Figma](https://www.figma.com/design/E1rC3MG74qEg5V4tvbeUnU/Earn?node-id=8418-38843&t=JGtN2YLsGFMZeYcS-0)

### Test plan

Unit tests, manual

Updated swap & deposit option and description:

<img
src="https://github.com/user-attachments/assets/1f376276-c557-49a8-bdc2-6e9b37ae0346"
width="250" />

<img
src="https://github.com/user-attachments/assets/aabee4fa-bfd1-4ff6-a6f9-6605c396cff0"
width="250" />

<img
src="https://github.com/user-attachments/assets/98316847-0f83-4c2b-bf4b-ad51b0c71edd"
width="250" />




### Related issues

- Part of ACT-1507

### Backwards compatibility

Yes

### Network scalability

N/A
  • Loading branch information
satish-ravi authored Jan 2, 2025
1 parent 8d74b49 commit 3305182
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 34 deletions.
2 changes: 2 additions & 0 deletions locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2785,12 +2785,14 @@
"youNeedTitle": "You Need {{tokenSymbol}} on {{tokenNetwork}} to Deposit",
"depositTitle": "Deposit to pool",
"crossChainAlternativeDescription": "If you don’t want to use your tokens on {{tokenNetwork}}, choose an option below. You’ll need to return to complete your pool deposit later.",
"alternativeDescription": "If you don’t want to use your tokens, choose an option below. You’ll need to return to complete your pool deposit later.",
"beforeYouCanDepositTitle": "Before you can deposit...",
"beforeYouCanDepositDescription": "You’ll need to add one of the pool tokens. Once added, you’ll need to return to complete your pool deposit.",
"beforeYouCanDepositDescriptionV1_101": "You’ll need to add {{tokenSymbol}} on {{tokenNetwork}}. Once added, you’ll need to return to complete your pool deposit.",
"action": {
"swapAndDeposit": "Swap & Deposit",
"swapAndDepositDescription": "Choose any token on {{tokenNetwork}}. We’ll swap and deposit it simultaneously for you.",
"swapAndDepositAllTokensDescription": "Choose any token—we’ll swap and deposit it simultaneously for you.",
"crossChainSwap": "Cross-chain Swap",
"crossChainSwapDescription": "Swap a token on another network for {{tokenSymbol}}",
"swap": "Swap",
Expand Down
142 changes: 139 additions & 3 deletions src/earn/poolInfoScreen/BeforeDepositBottomSheet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('BeforeDepositBottomSheet', () => {
${'has all types of tokens, cross chain swap disabled, cannot buy'} | ${['Deposit', 'SwapAndDeposit', 'Transfer']} | ${true} | ${true} | ${true} | ${false} | ${false} | ${false}
${'has all types of tokens, cannot buy'} | ${['Deposit', 'SwapAndDeposit', 'CrossChainSwap']} | ${true} | ${true} | ${true} | ${false} | ${false} | ${true}
`(
'shows correct title and actions when user $scenario',
'shows correct title and actions when cross chain swap and deposit is disabled and user $scenario',
({
hasDepositToken,
hasTokensOnSameNetwork,
Expand All @@ -80,7 +80,7 @@ describe('BeforeDepositBottomSheet', () => {
.mockImplementation(
(gate) => gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS && allowCrossChainSwaps
)
const { getAllByTestId, getByText, queryByTestId } = render(
const { getAllByTestId, getByText, queryByTestId, queryByText } = render(
<Provider store={createMockStore()}>
<BeforeDepositBottomSheet
forwardedRef={{ current: null }}
Expand All @@ -102,17 +102,153 @@ describe('BeforeDepositBottomSheet', () => {
expect(getAllByTestId(/^Earn\/ActionCard/).map((element) => element.props.testID)).toEqual(
expectedActions.map((action) => `Earn/ActionCard/${action}`)
)
const canDeposit =
expectedActions.includes('Deposit') || expectedActions.includes('SwapAndDeposit')
expect(
getByText(
`earnFlow.beforeDepositBottomSheet.${expectedActions.includes('Deposit') || expectedActions.includes('SwapAndDeposit') ? 'depositTitle' : 'beforeYouCanDepositTitle'}`
`earnFlow.beforeDepositBottomSheet.${canDeposit ? 'depositTitle' : 'beforeYouCanDepositTitle'}`
)
).toBeTruthy()
expect(!!queryByTestId('Earn/BeforeDepositBottomSheet/AlternativeDescription')).toBe(
canDeposit
)
expect(
!!queryByText(
'earnFlow.beforeDepositBottomSheet.crossChainAlternativeDescription, {"tokenNetwork":"Arbitrum Sepolia"}'
)
).toBe(canDeposit)
}
)

// The has other tokens case either sets hasTokensOnSameNetwork or
// hasTokensOnOtherNetworks to true, we don't need to test every combination individually
it.each`
scenario | expectedActions | hasDepositToken | hasTokensOnSameNetwork | hasTokensOnOtherNetworks | canAdd | poolCannotSwapAndDeposit
${'does not have any tokens'} | ${['Add', 'Transfer']} | ${false} | ${false} | ${false} | ${true} | ${false}
${'does not have any tokens, cannot buy'} | ${['Transfer']} | ${false} | ${false} | ${false} | ${false} | ${false}
${'only has deposit token'} | ${['Deposit', 'AddMore']} | ${true} | ${false} | ${false} | ${true} | ${false}
${'only has deposit token, cannot buy'} | ${['Deposit', 'Transfer']} | ${true} | ${false} | ${false} | ${false} | ${false}
${'only has other tokens'} | ${['SwapAndDeposit', 'Add']} | ${false} | ${true} | ${false} | ${true} | ${false}
${'only has other tokens, cannot buy'} | ${['SwapAndDeposit', 'Transfer']} | ${false} | ${false} | ${true} | ${false} | ${false}
${'only has other tokens, pool cannot swap and deposit'} | ${['Swap', 'Add']} | ${false} | ${true} | ${false} | ${true} | ${true}
${'only has other tokens, pool cannot swap and deposit, cannot buy'} | ${['Swap', 'Transfer']} | ${false} | ${true} | ${true} | ${false} | ${true}
${'has deposit token and other tokens'} | ${['Deposit', 'SwapAndDeposit', 'AddMore']} | ${true} | ${false} | ${true} | ${true} | ${false}
${'has deposit token and other tokens, cannot buy'} | ${['Deposit', 'SwapAndDeposit', 'Transfer']} | ${true} | ${true} | ${false} | ${false} | ${false}
${'has deposit token and other tokens, pool cannot swap and deposit'} | ${['Deposit', 'Swap', 'AddMore']} | ${true} | ${true} | ${true} | ${true} | ${true}
${'has deposit token and other tokens, pool cannot swap and deposit, cannot buy'} | ${['Deposit', 'Swap', 'Transfer']} | ${true} | ${true} | ${true} | ${false} | ${true}
`(
'shows correct title and actions when cross chain swap and deposit is enabled and user $scenario',
({
hasDepositToken,
hasTokensOnSameNetwork,
hasTokensOnOtherNetworks,
canAdd,
expectedActions,
poolCannotSwapAndDeposit,
}: {
hasDepositToken: boolean
hasTokensOnSameNetwork: boolean
hasTokensOnOtherNetworks: boolean
canAdd: boolean
expectedActions: string[]
poolCannotSwapAndDeposit: boolean
}) => {
jest
.mocked(getFeatureGate)
.mockImplementation(
(gate) =>
gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAP_AND_DEPOSIT ||
gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS
)
const { getAllByTestId, getByText, queryByTestId, queryByText } = render(
<Provider store={createMockStore()}>
<BeforeDepositBottomSheet
forwardedRef={{ current: null }}
token={mockToken}
pool={{
...mockEarnPositions[0],
availableShortcutIds: poolCannotSwapAndDeposit
? ['deposit', 'withdraw']
: mockEarnPositions[0].availableShortcutIds,
}}
hasDepositToken={hasDepositToken}
hasTokensOnSameNetwork={hasTokensOnSameNetwork}
hasTokensOnOtherNetworks={hasTokensOnOtherNetworks}
canAdd={canAdd}
exchanges={[]}
/>
</Provider>
)
expect(getAllByTestId(/^Earn\/ActionCard/).map((element) => element.props.testID)).toEqual(
expectedActions.map((action) => `Earn/ActionCard/${action}`)
)
const canDeposit =
expectedActions.includes('Deposit') || expectedActions.includes('SwapAndDeposit')
expect(
getByText(
`earnFlow.beforeDepositBottomSheet.${canDeposit ? 'depositTitle' : 'beforeYouCanDepositTitle'}`
)
).toBeTruthy()
expect(!!queryByTestId('Earn/BeforeDepositBottomSheet/AlternativeDescription')).toBe(
canDeposit
)
expect(!!queryByText('earnFlow.beforeDepositBottomSheet.alternativeDescription')).toBe(
canDeposit
)
}
)

it('shows correct swap and deposit action description when cross chain swap and deposit is disabled', () => {
const { getByTestId, getByText } = render(
<Provider store={createMockStore()}>
<BeforeDepositBottomSheet
forwardedRef={{ current: null }}
token={mockToken}
pool={mockEarnPositions[0]}
hasDepositToken={true}
hasTokensOnSameNetwork={true}
hasTokensOnOtherNetworks={true}
canAdd={true}
exchanges={[]}
/>
</Provider>
)
expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toBeTruthy()
expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toContainElement(
getByText(
'earnFlow.beforeDepositBottomSheet.action.swapAndDepositDescription, {"tokenNetwork":"Arbitrum Sepolia"}'
)
)
})

it('shows correct swap and deposit action description when cross chain swap and deposit is enabled', () => {
jest
.mocked(getFeatureGate)
.mockImplementation(
(gate) =>
gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAP_AND_DEPOSIT ||
gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS
)
const { getByTestId, getByText } = render(
<Provider store={createMockStore()}>
<BeforeDepositBottomSheet
forwardedRef={{ current: null }}
token={mockToken}
pool={mockEarnPositions[0]}
hasDepositToken={true}
hasTokensOnSameNetwork={true}
hasTokensOnOtherNetworks={true}
canAdd={true}
exchanges={[]}
/>
</Provider>
)
expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toBeTruthy()
expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toContainElement(
getByText('earnFlow.beforeDepositBottomSheet.action.swapAndDepositAllTokensDescription')
)
})

it('navigates correctly when deposit action item is tapped', () => {
const { getByTestId } = render(
<Provider store={createMockStore()}>
Expand Down
103 changes: 72 additions & 31 deletions src/earn/poolInfoScreen/BeforeDepositBottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { RefObject } from 'react'
import React, { RefObject, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import AppAnalytics from 'src/analytics/AppAnalytics'
Expand Down Expand Up @@ -200,21 +200,24 @@ function SwapAndDepositAction({
pool,
forwardedRef,
analyticsProps,
allowCrossChainSwapAndDeposit,
}: {
token: TokenBalance
pool: EarnPosition
forwardedRef: React.RefObject<BottomSheetModalRefType>
analyticsProps: EarnCommonProperties & TokenProperties
allowCrossChainSwapAndDeposit: boolean
}) {
const { t } = useTranslation()

const action: BeforeDepositAction = {
name: 'SwapAndDeposit',
title: t('earnFlow.beforeDepositBottomSheet.action.swapAndDeposit'),
details: t('earnFlow.beforeDepositBottomSheet.action.swapAndDepositDescription', {
tokenSymbol: token.symbol,
tokenNetwork: NETWORK_NAMES[token.networkId],
}),
details: allowCrossChainSwapAndDeposit
? t('earnFlow.beforeDepositBottomSheet.action.swapAndDepositAllTokensDescription')
: t('earnFlow.beforeDepositBottomSheet.action.swapAndDepositDescription', {
tokenNetwork: NETWORK_NAMES[token.networkId],
}),
iconComponent: SwapAndDeposit,
onPress: () => {
AppAnalytics.track(EarnEvents.earn_before_deposit_action_press, {
Expand Down Expand Up @@ -284,38 +287,73 @@ export default function BeforeDepositBottomSheet({
}) {
const { t } = useTranslation()

const { availableShortcutIds } = pool
const allowCrossChainSwaps = getFeatureGate(StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS)
const canCrossChainSwap = allowCrossChainSwaps && hasTokensOnOtherNetworks

const canSwapDeposit = availableShortcutIds.includes('swap-deposit') && hasTokensOnSameNetwork

const title =
canSwapDeposit || hasDepositToken
? t('earnFlow.beforeDepositBottomSheet.depositTitle')
: t('earnFlow.beforeDepositBottomSheet.beforeYouCanDepositTitle')

const analyticsProps = {
...getTokenAnalyticsProps(token),
poolId: pool.positionId,
providerId: pool.appId,
depositTokenId: pool.dataProps.depositTokenId,
}

const showCrossChainSwap = canSwapDeposit && canCrossChainSwap
// Show a generic swap option if the pool doesn't support swap and deposit.
const showSwap = !canSwapDeposit && (hasTokensOnSameNetwork || canCrossChainSwap)
const { availableShortcutIds } = pool
const allowCrossChainSwaps = getFeatureGate(StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS)
const allowCrossChainSwapAndDeposit = getFeatureGate(
StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAP_AND_DEPOSIT
)

const { canSwapDeposit, showCrossChainSwap, showSwap, showAdd, showAddMore, showTransfer } =
useMemo(() => {
if (allowCrossChainSwapAndDeposit) {
const hasOtherTokens = hasTokensOnOtherNetworks || hasTokensOnSameNetwork
// should never have a case where allowCrossChainSwapAndDeposit is true
// and allowCrossChainSwaps is false, so just presence of any token
// should enable swap / swap and deposit
const canSwapDeposit = availableShortcutIds.includes('swap-deposit') && hasOtherTokens
return {
canSwapDeposit,
// no longer need to show separate cross chain swap action
showCrossChainSwap: false,
showSwap: !canSwapDeposit && hasOtherTokens,
showAdd: canAdd && !hasDepositToken,
showAddMore: canAdd && hasDepositToken,
// Show Transfer if add is not an option or if the user has no tokens
showTransfer: !canAdd || (!hasDepositToken && !hasOtherTokens),
}
} else {
const canCrossChainSwap = allowCrossChainSwaps && hasTokensOnOtherNetworks
const hasAllDepositAndSwapOptions =
hasDepositToken && hasTokensOnSameNetwork && canCrossChainSwap
const hasNoDepositOrSwapOptions =
!hasDepositToken && !hasTokensOnSameNetwork && !canCrossChainSwap

const hasAllDepositAndSwapOptions = hasDepositToken && hasTokensOnSameNetwork && canCrossChainSwap
const hasNoDepositOrSwapOptions =
!hasDepositToken && !hasTokensOnSameNetwork && !canCrossChainSwap
const canSwapDeposit =
availableShortcutIds.includes('swap-deposit') && hasTokensOnSameNetwork

const showAdd = canAdd && !hasDepositToken
// Don't show add more if the user has all deposit and swap options are available
const showAddMore = canAdd && hasDepositToken && !hasAllDepositAndSwapOptions
// Show Transfer if the user cannot deposit or swap options or if the token
// does not support buy and if not all deposit and swap options are available
const showTransfer = hasNoDepositOrSwapOptions || (!canAdd && !hasAllDepositAndSwapOptions)
return {
canSwapDeposit,
showCrossChainSwap: canSwapDeposit && canCrossChainSwap,
showSwap: !canSwapDeposit && (hasTokensOnSameNetwork || canCrossChainSwap),
showAdd: canAdd && !hasDepositToken,
// Don't show add more if the user has all deposit and swap options are available
showAddMore: canAdd && hasDepositToken && !hasAllDepositAndSwapOptions,
// Show Transfer if the user has no deposit or swap options or if the token
// does not support buy and if not all deposit and swap options are available
showTransfer: hasNoDepositOrSwapOptions || (!canAdd && !hasAllDepositAndSwapOptions),
}
}
}, [
hasDepositToken,
hasTokensOnSameNetwork,
hasTokensOnOtherNetworks,
canAdd,
availableShortcutIds,
allowCrossChainSwapAndDeposit,
allowCrossChainSwaps,
])

const title =
canSwapDeposit || hasDepositToken
? t('earnFlow.beforeDepositBottomSheet.depositTitle')
: t('earnFlow.beforeDepositBottomSheet.beforeYouCanDepositTitle')

return (
<BottomSheet
Expand Down Expand Up @@ -347,6 +385,7 @@ export default function BeforeDepositBottomSheet({
pool={pool}
forwardedRef={forwardedRef}
analyticsProps={analyticsProps}
allowCrossChainSwapAndDeposit={allowCrossChainSwapAndDeposit}
/>
)}
{(canSwapDeposit || hasDepositToken) &&
Expand All @@ -355,9 +394,11 @@ export default function BeforeDepositBottomSheet({
testID={'Earn/BeforeDepositBottomSheet/AlternativeDescription'}
style={styles.actionDetails}
>
{t('earnFlow.beforeDepositBottomSheet.crossChainAlternativeDescription', {
tokenNetwork: NETWORK_NAMES[token.networkId],
})}
{allowCrossChainSwapAndDeposit
? t('earnFlow.beforeDepositBottomSheet.alternativeDescription')
: t('earnFlow.beforeDepositBottomSheet.crossChainAlternativeDescription', {
tokenNetwork: NETWORK_NAMES[token.networkId],
})}
</Text>
)}
{showCrossChainSwap && (
Expand Down

0 comments on commit 3305182

Please sign in to comment.