Skip to content

Commit

Permalink
feat(earn): Review & Deposit updates - APY and earn up to amount (#5449)
Browse files Browse the repository at this point in the history
### Description

Review and deposit fast follows
- Generalizes the APY and earn up to component 
- Uses it in the Review and Deposit screen

### Test plan
Unit tests added/updated.

Enter amount (looks same as before):

![enter-amount](https://github.com/valora-inc/wallet/assets/140328381/7416db76-246c-486b-bc76-090d013b3873)

Deposit:

![deposit](https://github.com/valora-inc/wallet/assets/140328381/db121d61-a8e3-446b-83f8-fe6dd2fdceee)

### Related issues

- Fixes #ACT-1195

### Backwards compatibility

Yes

### Network scalability

If a new NetworkId and/or Network are added in the future, the changes
in this PR will:

- [X] Continue to work without code changes, OR trigger a compilation
error (guaranteeing we find it when a new network is added)
  • Loading branch information
finnian0826 committed May 23, 2024
1 parent ed44f1f commit 48373ce
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 77 deletions.
82 changes: 82 additions & 0 deletions src/earn/EarnApyAndAmount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import BigNumber from 'bignumber.js'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { Colors } from 'react-native/Libraries/NewAppScreen'
import { useSelector } from 'react-redux'
import TokenIcon, { IconSize } from 'src/components/TokenIcon'
import { useAavePoolInfo } from 'src/earn/hooks'
import { LocalCurrencySymbol } from 'src/localCurrency/consts'
import { getLocalCurrencySymbol } from 'src/localCurrency/selectors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import { TokenBalance } from 'src/tokens/slice'

export function EarnApyAndAmount({
tokenAmount,
token,
testIDPrefix = 'Earn',
}: {
tokenAmount: BigNumber | null
token: TokenBalance
testIDPrefix?: string
}) {
const { t } = useTranslation()

const localCurrencySymbol = useSelector(getLocalCurrencySymbol) ?? LocalCurrencySymbol.USD

const asyncPoolInfo = useAavePoolInfo({ depositTokenId: token.tokenId })
const apy = asyncPoolInfo?.result?.apy

const apyString = apy ? (apy * 100).toFixed(2) : '--'
const earnUpTo =
apy && tokenAmount?.gt(0) ? tokenAmount.multipliedBy(new BigNumber(apy)).toFormat(2) : '--'

return (
<>
<View style={styles.line}>
<Text style={styles.label}>{t('earnFlow.enterAmount.earnUpToLabel')}</Text>
<Text style={styles.label}>{t('earnFlow.enterAmount.rateLabel')}</Text>
</View>
<View style={styles.line}>
<Text style={styles.valuesText} testID={`${testIDPrefix}/EarnApyAndAmount/EarnUpTo`}>
{t('earnFlow.enterAmount.earnUpTo', {
fiatSymbol: localCurrencySymbol,
amount: earnUpTo,
})}
</Text>
<View style={styles.apy}>
<TokenIcon token={token} size={IconSize.XSMALL} />
<Text style={styles.valuesText} testID={`${testIDPrefix}/EarnApyAndAmount/Apy`}>
{t('earnFlow.enterAmount.rate', {
rate: apyString,
})}
</Text>
</View>
</View>
</>
)
}

const styles = StyleSheet.create({
apy: {
flexDirection: 'row',
alignItems: 'center',
gap: Spacing.Tiny4,
},
label: {
...typeScale.bodySmall,
color: Colors.gray4,
},
line: {
flexDirection: 'row',
alignSelf: 'flex-end',
justifyContent: 'space-between',
width: '100%',
gap: Spacing.Smallest8,
},
valuesText: {
...typeScale.labelSemiBoldSmall,
marginVertical: Spacing.Tiny4,
},
})
35 changes: 20 additions & 15 deletions src/earn/EarnDepositBottomSheet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { StatsigDynamicConfigs, StatsigFeatureGates } from 'src/statsig/types'
import { NetworkId } from 'src/transactions/types'
import { PreparedTransactionsPossible } from 'src/viem/prepareTransactions'
import { getSerializablePreparedTransactions } from 'src/viem/preparedTransactionSerialization'
import { createMockStore } from 'test/utils'
import { createMockStore, mockStoreBalancesToTokenBalances } from 'test/utils'
import { mockArbEthTokenId, mockTokenBalances } from 'test/values'

jest.mock('src/statsig')
Expand Down Expand Up @@ -49,6 +49,8 @@ const mockPreparedTransaction: PreparedTransactionsPossible = {
},
}

const mockToken = mockStoreBalancesToTokenBalances([mockTokenBalances[mockArbEthTokenId]])[0]

describe('EarnDepositBottomSheet', () => {
const expectedAnalyticsProperties = {
depositTokenId: mockArbEthTokenId,
Expand Down Expand Up @@ -79,16 +81,19 @@ describe('EarnDepositBottomSheet', () => {
<Provider store={createMockStore({ tokens: { tokenBalances: mockTokenBalances } })}>
<EarnDepositBottomSheet
forwardedRef={{ current: null }}
amount={'100'}
tokenId={mockArbEthTokenId}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
/>
</Provider>
)
expect(getByText('earnFlow.depositBottomSheet.title')).toBeTruthy()
expect(getByText('earnFlow.depositBottomSheet.description')).toBeTruthy()

expect(getByTestId('EarnDepositBottomSheet/EarnApyAndAmount/Apy')).toBeTruthy()
expect(getByTestId('EarnDepositBottomSheet/EarnApyAndAmount/EarnUpTo')).toBeTruthy()

expect(queryByTestId('EarnDeposit/GasSubsidized')).toBeFalsy()

expect(getByText('earnFlow.depositBottomSheet.amount')).toBeTruthy()
Expand Down Expand Up @@ -116,9 +121,9 @@ describe('EarnDepositBottomSheet', () => {
<Provider store={store}>
<EarnDepositBottomSheet
forwardedRef={{ current: null }}
amount={'100'}
tokenId={mockArbEthTokenId}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
/>
</Provider>
Expand Down Expand Up @@ -148,9 +153,9 @@ describe('EarnDepositBottomSheet', () => {
<Provider store={createMockStore({ tokens: { tokenBalances: mockTokenBalances } })}>
<EarnDepositBottomSheet
forwardedRef={{ current: null }}
amount={'100'}
tokenId={mockArbEthTokenId}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
/>
</Provider>
Expand All @@ -168,9 +173,9 @@ describe('EarnDepositBottomSheet', () => {
<Provider store={createMockStore({ tokens: { tokenBalances: mockTokenBalances } })}>
<EarnDepositBottomSheet
forwardedRef={{ current: null }}
amount={'100'}
tokenId={mockArbEthTokenId}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
/>
</Provider>
Expand All @@ -189,9 +194,9 @@ describe('EarnDepositBottomSheet', () => {
<Provider store={createMockStore({ tokens: { tokenBalances: mockTokenBalances } })}>
<EarnDepositBottomSheet
forwardedRef={{ current: null }}
amount={'100'}
tokenId={mockArbEthTokenId}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
/>
</Provider>
Expand All @@ -214,9 +219,9 @@ describe('EarnDepositBottomSheet', () => {
<Provider store={store}>
<EarnDepositBottomSheet
forwardedRef={{ current: null }}
amount={'100'}
tokenId={mockArbEthTokenId}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
/>
</Provider>
Expand All @@ -238,9 +243,9 @@ describe('EarnDepositBottomSheet', () => {
<Provider store={createMockStore({ tokens: { tokenBalances: mockTokenBalances } })}>
<EarnDepositBottomSheet
forwardedRef={{ current: null }}
amount={'100'}
tokenId={mockArbEthTokenId}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
/>
</Provider>
Expand Down
32 changes: 24 additions & 8 deletions src/earn/EarnDepositBottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BigNumber from 'bignumber.js'
import React, { RefObject } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
Expand All @@ -10,6 +11,7 @@ import BottomSheet, { BottomSheetRefType } from 'src/components/BottomSheet'
import Button, { BtnSizes, BtnTypes } from 'src/components/Button'
import TokenDisplay from 'src/components/TokenDisplay'
import Touchable from 'src/components/Touchable'
import { EarnApyAndAmount } from 'src/earn/EarnApyAndAmount'
import { PROVIDER_ID } from 'src/earn/constants'
import { depositStatusSelector } from 'src/earn/selectors'
import { depositStart } from 'src/earn/slice'
Expand All @@ -25,6 +27,7 @@ import { StatsigDynamicConfigs, StatsigFeatureGates } from 'src/statsig/types'
import Colors from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Shadow, Spacing, getShadowStyle } from 'src/styles/styles'
import { TokenBalance } from 'src/tokens/slice'
import { NetworkId } from 'src/transactions/types'
import {
PreparedTransactionsPossible,
Expand All @@ -38,13 +41,13 @@ export default function EarnDepositBottomSheet({
forwardedRef,
preparedTransaction,
amount,
tokenId,
token,
networkId,
}: {
forwardedRef: RefObject<BottomSheetRefType>
preparedTransaction: PreparedTransactionsPossible
amount: string
tokenId: string
amount: BigNumber
token: TokenBalance
networkId: NetworkId
}) {
const { t } = useTranslation()
Expand All @@ -54,8 +57,8 @@ export default function EarnDepositBottomSheet({

const commonAnalyticsProperties = {
providerId: PROVIDER_ID,
depositTokenId: tokenId,
tokenAmount: amount,
depositTokenId: token.tokenId,
tokenAmount: amount.toString(),
networkId,
}

Expand Down Expand Up @@ -90,8 +93,8 @@ export default function EarnDepositBottomSheet({
const onPressComplete = () => {
dispatch(
depositStart({
amount,
tokenId,
amount: amount.toString(),
tokenId: token.tokenId,
preparedTransactions: getSerializablePreparedTransactions(preparedTransaction.transactions),
})
)
Expand All @@ -109,11 +112,18 @@ export default function EarnDepositBottomSheet({
<Logos providerUrl={providerLogoUrl} />
<Text style={styles.title}>{t('earnFlow.depositBottomSheet.title')}</Text>
<Text style={styles.description}>{t('earnFlow.depositBottomSheet.description')}</Text>
<View style={styles.infoContainer}>
<EarnApyAndAmount
tokenAmount={amount}
token={token}
testIDPrefix={'EarnDepositBottomSheet'}
/>
</View>
<LabelledItem label={t('earnFlow.depositBottomSheet.amount')}>
<TokenDisplay
testID="EarnDeposit/Amount"
amount={amount}
tokenId={tokenId}
tokenId={token.tokenId}
style={styles.value}
showLocalAmount={false}
/>
Expand Down Expand Up @@ -273,6 +283,12 @@ const styles = StyleSheet.create({
flexGrow: 1,
flexBasis: 0,
},
infoContainer: {
padding: Spacing.Regular16,
borderRadius: 16,
borderWidth: 1,
borderColor: Colors.gray2,
},
gasSubsidized: {
...typeScale.labelXSmall,
color: Colors.primary,
Expand Down
4 changes: 2 additions & 2 deletions src/earn/EarnEnterAmount.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ describe('EarnEnterAmount', () => {
<MockedNavigator component={EarnEnterAmount} params={params} />
</Provider>
)
expect(getByTestId('EarnEnterAmount/Apy')).toBeTruthy()
expect(getByTestId('EarnEnterAmount/EarnUpTo')).toBeTruthy()
expect(getByTestId('EarnEnterAmount/EarnApyAndAmount/Apy')).toBeTruthy()
expect(getByTestId('EarnEnterAmount/EarnApyAndAmount/EarnUpTo')).toBeTruthy()
})

it('should be able to tap info icon', async () => {
Expand Down
56 changes: 4 additions & 52 deletions src/earn/EarnEnterAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import TokenIcon, { IconSize } from 'src/components/TokenIcon'
import Touchable from 'src/components/Touchable'
import CustomHeader from 'src/components/header/CustomHeader'
import EarnAddCryptoBottomSheet from 'src/earn/EarnAddCryptoBottomSheet'
import { EarnApyAndAmount } from 'src/earn/EarnApyAndAmount'
import EarnDepositBottomSheet from 'src/earn/EarnDepositBottomSheet'
import { PROVIDER_ID } from 'src/earn/constants'
import { useAavePoolInfo } from 'src/earn/hooks'
import { usePrepareSupplyTransactions } from 'src/earn/prepareTransactions'
import { CICOFlow } from 'src/fiatExchanges/utils'
import InfoIcon from 'src/icons/InfoIcon'
Expand Down Expand Up @@ -376,8 +376,8 @@ function EarnEnterAmount({ route }: Props) {
<EarnDepositBottomSheet
forwardedRef={reviewBottomSheetRef}
preparedTransaction={prepareTransactionsResult}
amount={tokenAmount.toString()}
tokenId={token.tokenId}
amount={tokenAmount}
token={token}
networkId={token.networkId}
/>
)}
Expand All @@ -395,38 +395,10 @@ function EarnProceed({
onPressInfo,
}: ProceedComponentProps) {
const { t } = useTranslation()
const localCurrencySymbol = useSelector(getLocalCurrencySymbol)

const asyncPoolInfo = useAavePoolInfo({ depositTokenId: token.tokenId })

return (
<View style={styles.infoContainer}>
<View style={styles.line}>
<Text style={styles.label}>{t('earnFlow.enterAmount.earnUpToLabel')}</Text>
<Text style={styles.label}>{t('earnFlow.enterAmount.rateLabel')}</Text>
</View>
<View style={styles.line}>
<Text style={styles.valuesText} testID="EarnEnterAmount/EarnUpTo">
{t('earnFlow.enterAmount.earnUpTo', {
fiatSymbol: localCurrencySymbol,
amount:
asyncPoolInfo?.result && !!asyncPoolInfo.result.apy && tokenAmount?.gt(0)
? tokenAmount.multipliedBy(new BigNumber(asyncPoolInfo.result.apy)).toFormat(2)
: '--',
})}
</Text>
<View style={styles.apy}>
<TokenIcon token={token} size={IconSize.XSMALL} />
<Text style={styles.valuesText} testID="EarnEnterAmount/Apy">
{t('earnFlow.enterAmount.rate', {
rate:
asyncPoolInfo?.result && !!asyncPoolInfo.result.apy
? (asyncPoolInfo.result.apy * 100).toFixed(2)
: '--',
})}
</Text>
</View>
</View>
<EarnApyAndAmount tokenAmount={tokenAmount} token={token} testIDPrefix={'EarnEnterAmount'} />
<Button
onPress={() =>
tokenAmount && onPressProceed({ tokenAmount, localAmount, token, amountEnteredIn })
Expand Down Expand Up @@ -502,36 +474,16 @@ const styles = StyleSheet.create({
borderColor: Colors.gray2,
marginTop: 20,
},
line: {
flexDirection: 'row',
alignSelf: 'flex-end',
justifyContent: 'space-between',
width: '100%',
gap: Spacing.Smallest8,
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
gap: Spacing.Tiny4,
},
apy: {
flexDirection: 'row',
alignItems: 'center',
gap: Spacing.Tiny4,
},
label: {
...typeScale.bodySmall,
color: Colors.gray4,
},
continueButton: {
paddingVertical: Spacing.Thick24,
},
valuesText: {
...typeScale.labelSemiBoldSmall,
marginVertical: Spacing.Tiny4,
},
infoText: {
...typeScale.bodyXSmall,
color: Colors.gray4,
Expand Down

0 comments on commit 48373ce

Please sign in to comment.