Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(earn): use earn position in deposit screens #5830

Merged
merged 8 commits into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2537,6 +2537,7 @@
"rate": "{{rate}}% APY",
"continue": "Continue",
"info": "This pool is powered by Aave",
"infoV1_93": "Powered by {{providerName}}",
"infoBottomSheet": {
"title": "Why this pool?",
"description": "This Aave pool has over $150M TVL and more than 6,000 contributors, indicating strong liquidity and trustworthiness. It also uses USDC as its token and Arbitrum as its network, which reduces volatility and ensures users will receive inexpensive gas rates.\n\nAll together this makes it a safe and attractive option with competitive APY, ensuring a beneficial investment opportunity for our users.\n\nYou can explore other Aave pools <0>here</0>.",
Expand Down
14 changes: 10 additions & 4 deletions src/earn/EarnActivePool.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { fireEvent, render } from '@testing-library/react-native'
import React from 'react'
import { Provider } from 'react-redux'
import { EarnEvents } from 'src/analytics/Events'
import AppAnalytics from 'src/analytics/AppAnalytics'
import { EarnEvents } from 'src/analytics/Events'
import EarnActivePool from 'src/earn/EarnActivePool'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
Expand All @@ -11,7 +11,7 @@ import { StatsigFeatureGates } from 'src/statsig/types'
import { NetworkId } from 'src/transactions/types'
import networkConfig from 'src/web3/networkConfig'
import { createMockStore } from 'test/utils'
import { mockAaveArbUsdcAddress } from 'test/values'
import { mockAaveArbUsdcAddress, mockEarnPositions } from 'test/values'

const store = createMockStore({
tokens: {
Expand All @@ -27,14 +27,20 @@ const store = createMockStore({
},
},
},
positions: {
positions: mockEarnPositions,
earnPositionIds: mockEarnPositions.map((position) => position.positionId),
},
})

jest.mock('src/statsig')

describe('EarnActivePool', () => {
beforeEach(() => {
jest.clearAllMocks()
jest.mocked(getFeatureGate).mockReturnValue(false)
jest
.mocked(getFeatureGate)
.mockImplementation((gate) => gate === StatsigFeatureGates.SHOW_POSITIONS)
})

it('should render correctly with ExitAndDeposit cta', () => {
Expand Down Expand Up @@ -153,7 +159,7 @@ describe('EarnActivePool', () => {
networkId: NetworkId['arbitrum-sepolia'],
})
expect(navigate).toBeCalledWith(Screens.EarnEnterAmount, {
tokenId: networkConfig.arbUsdcTokenId,
pool: mockEarnPositions[0],
})
})
})
6 changes: 4 additions & 2 deletions src/earn/EarnActivePool.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { EarnEvents } from 'src/analytics/Events'
import AppAnalytics from 'src/analytics/AppAnalytics'
import { EarnEvents } from 'src/analytics/Events'
import Button, { BtnSizes, BtnTypes } from 'src/components/Button'
import SkeletonPlaceholder from 'src/components/SkeletonPlaceholder'
import TokenDisplay from 'src/components/TokenDisplay'
import { PROVIDER_ID } from 'src/earn/constants'
import { useEarnPosition } from 'src/earn/hooks'
import { poolInfoFetchStatusSelector, poolInfoSelector } from 'src/earn/selectors'
import { fetchPoolInfo } from 'src/earn/slice'
import { navigate } from 'src/navigator/NavigationService'
Expand Down Expand Up @@ -50,6 +51,7 @@ export default function EarnActivePool({ depositTokenId, poolTokenId, cta }: Pro
const poolToken = useTokenInfo(poolTokenId)
const poolInfo = useSelector(poolInfoSelector)
const poolInfoFetchStatus = useSelector(poolInfoFetchStatusSelector)
const earnPosition = useEarnPosition(poolTokenId)

useEffect(() => {
dispatch(fetchPoolInfo())
Expand Down Expand Up @@ -125,7 +127,7 @@ export default function EarnActivePool({ depositTokenId, poolTokenId, cta }: Pro
providerId: PROVIDER_ID,
networkId: poolToken.networkId,
})
navigate(Screens.EarnEnterAmount, { tokenId: depositTokenId })
earnPosition && navigate(Screens.EarnEnterAmount, { pool: earnPosition })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens here if getting the earnPosition fails? Can the user just refresh and then click?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the positions I believe are fetched every time the balance is refreshed. So refresh would work. We can add an error state but doesn't seem worth it here

}}
text={t('earnFlow.activePools.depositMore')}
type={BtnTypes.PRIMARY}
Expand Down
57 changes: 19 additions & 38 deletions src/earn/EarnApyAndAmount.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,38 @@
import BigNumber from 'bignumber.js'
import React, { useEffect } from 'react'
import React from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import SkeletonPlaceholder from 'src/components/SkeletonPlaceholder'
import TokenDisplay from 'src/components/TokenDisplay'
import TokenIcon, { IconSize } from 'src/components/TokenIcon'
import { poolInfoFetchStatusSelector, poolInfoSelector } from 'src/earn/selectors'
import { fetchPoolInfo } from 'src/earn/slice'
import { useDispatch, useSelector } from 'src/redux/hooks'
import { getTotalYieldRate } from 'src/earn/poolInfo'
import { EarnPosition } from 'src/positions/types'
import { Colors } from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import { TokenBalance } from 'src/tokens/slice'
import { useTokenInfo } from 'src/tokens/hooks'

export function EarnApyAndAmount({
tokenAmount,
token,
pool,
testIDPrefix = 'Earn',
}: {
tokenAmount: BigNumber | null
token: TokenBalance
pool: EarnPosition
testIDPrefix?: string
}) {
const { t } = useTranslation()
const dispatch = useDispatch()
const poolInfo = useSelector(poolInfoSelector)
const poolInfoFetchStatus = useSelector(poolInfoFetchStatusSelector)

useEffect(() => {
dispatch(fetchPoolInfo())
}, [])
const apy = getTotalYieldRate(pool)
const token = useTokenInfo(pool.dataProps.depositTokenId)

const apy = poolInfo?.apy
if (!token) {
// should never happen
throw new Error(`Token not found ${pool.dataProps.depositTokenId}`)

Check warning on line 30 in src/earn/EarnApyAndAmount.tsx

View check run for this annotation

Codecov / codecov/patch

src/earn/EarnApyAndAmount.tsx#L30

Added line #L30 was not covered by tests
}

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

return (
<>
Expand All @@ -57,21 +54,11 @@
<View style={styles.apy}>
<TokenIcon token={token} size={IconSize.XSMALL} />

{poolInfoFetchStatus === 'loading' ? (
<SkeletonPlaceholder
backgroundColor={Colors.gray2}
highlightColor={Colors.white}
testID={`${testIDPrefix}/EarnApyAndAmount/Apy/Loading`}
>
<View style={styles.loadingSkeleton} />
</SkeletonPlaceholder>
) : (
<Text style={styles.valuesText} testID={`${testIDPrefix}/EarnApyAndAmount/Apy`}>
{t('earnFlow.enterAmount.rate', {
rate: apyString,
})}
</Text>
)}
<Text style={styles.valuesText} testID={`${testIDPrefix}/EarnApyAndAmount/Apy`}>
{t('earnFlow.enterAmount.rate', {
rate: apyString,
})}
</Text>
</View>
</View>
</>
Expand Down Expand Up @@ -99,10 +86,4 @@
...typeScale.labelSemiBoldSmall,
marginVertical: Spacing.Tiny4,
},
loadingSkeleton: {
...typeScale.labelSemiBoldSmall,
marginVertical: Spacing.Smallest8,
width: 100,
borderRadius: 100,
},
})
51 changes: 21 additions & 30 deletions src/earn/EarnDepositBottomSheet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@ import { fireEvent, render } from '@testing-library/react-native'
import BigNumber from 'bignumber.js'
import React from 'react'
import { Provider } from 'react-redux'
import { EarnEvents } from 'src/analytics/Events'
import AppAnalytics from 'src/analytics/AppAnalytics'
import { EarnEvents } from 'src/analytics/Events'
import EarnDepositBottomSheet from 'src/earn/EarnDepositBottomSheet'
import { PROVIDER_ID } from 'src/earn/constants'
import { depositStart, fetchPoolInfo } from 'src/earn/slice'
import { depositStart } from 'src/earn/slice'
import { navigate } from 'src/navigator/NavigationService'
import { getDynamicConfigParams, getFeatureGate } from 'src/statsig'
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, mockStoreBalancesToTokenBalances } from 'test/utils'
import { mockArbEthTokenId, mockTokenBalances } from 'test/values'
import { createMockStore } from 'test/utils'
import {
mockArbEthTokenId,
mockArbUsdcTokenId,
mockEarnPositions,
mockTokenBalances,
} from 'test/values'

jest.mock('src/statsig')

Expand Down Expand Up @@ -49,14 +53,12 @@ const mockPreparedTransaction: PreparedTransactionsPossible = {
},
}

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

describe('EarnDepositBottomSheet', () => {
const expectedAnalyticsProperties = {
depositTokenId: mockArbEthTokenId,
depositTokenId: mockArbUsdcTokenId,
tokenAmount: '100',
networkId: NetworkId['arbitrum-sepolia'],
providerId: PROVIDER_ID,
providerId: mockEarnPositions[0].appId,
}

beforeEach(() => {
Expand All @@ -81,27 +83,25 @@ describe('EarnDepositBottomSheet', () => {
<Provider
store={createMockStore({
tokens: { tokenBalances: mockTokenBalances },
earn: { poolInfoFetchStatus: 'loading' },
})}
>
<EarnDepositBottomSheet
forwardedRef={{ current: null }}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
pool={mockEarnPositions[0]}
/>
</Provider>
)
expect(getByText('earnFlow.depositBottomSheet.title')).toBeTruthy()
expect(getByText('earnFlow.depositBottomSheet.description')).toBeTruthy()

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

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

expect(getByText('earnFlow.depositBottomSheet.amount')).toBeTruthy()
expect(getByTestId('EarnDeposit/Amount')).toHaveTextContent('100.00 ETH')
expect(getByTestId('EarnDeposit/Amount')).toHaveTextContent('100.00 USDC')

expect(getByText('earnFlow.depositBottomSheet.fee')).toBeTruthy()
expect(getByTestId('EarnDeposit/Fee')).toHaveTextContent('0.06 ETH')
Expand All @@ -127,8 +127,7 @@ describe('EarnDepositBottomSheet', () => {
forwardedRef={{ current: null }}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
pool={mockEarnPositions[0]}
/>
</Provider>
)
Expand All @@ -139,14 +138,11 @@ describe('EarnDepositBottomSheet', () => {
expectedAnalyticsProperties
)
expect(store.getActions()).toEqual([
{
type: fetchPoolInfo.type,
},
{
type: depositStart.type,
payload: {
amount: '100',
tokenId: mockArbEthTokenId,
tokenId: mockArbUsdcTokenId,
preparedTransactions: getSerializablePreparedTransactions(
mockPreparedTransaction.transactions
),
Expand All @@ -162,8 +158,7 @@ describe('EarnDepositBottomSheet', () => {
forwardedRef={{ current: null }}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
pool={mockEarnPositions[0]}
/>
</Provider>
)
Expand All @@ -182,8 +177,7 @@ describe('EarnDepositBottomSheet', () => {
forwardedRef={{ current: null }}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
pool={mockEarnPositions[0]}
/>
</Provider>
)
Expand All @@ -203,8 +197,7 @@ describe('EarnDepositBottomSheet', () => {
forwardedRef={{ current: null }}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
pool={mockEarnPositions[0]}
/>
</Provider>
)
Expand All @@ -228,8 +221,7 @@ describe('EarnDepositBottomSheet', () => {
forwardedRef={{ current: null }}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
pool={mockEarnPositions[0]}
/>
</Provider>
)
Expand All @@ -252,8 +244,7 @@ describe('EarnDepositBottomSheet', () => {
forwardedRef={{ current: null }}
amount={new BigNumber(100)}
preparedTransaction={mockPreparedTransaction}
token={mockToken}
networkId={NetworkId['arbitrum-sepolia']}
pool={mockEarnPositions[0]}
/>
</Provider>
)
Expand Down
Loading