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

feat: earn asset details for aaveUSDC #5403

Merged
merged 51 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
8f7a45d
feat: add aave pool fetch user balance function
MuckT May 7, 2024
38f2ce4
feat: add wrapper hooks for pool info functions
MuckT May 7, 2024
59f0a71
chore: improve debug logging for fetchAavePoolUserBalance
MuckT May 7, 2024
e45ec43
docs: exclude fetchAavePoolUserBalance from knip dep check
MuckT May 7, 2024
396922a
test: fetches user balance from aave contract
MuckT May 7, 2024
d50c4da
chore: remove hooks until usage is needed
MuckT May 7, 2024
5bc1b87
style: prefer address type from viem
MuckT May 7, 2024
3c133d6
docs: remove beta tag
MuckT May 7, 2024
3823125
fix: remove top checks
MuckT May 7, 2024
604e354
feat: add hooks for earn pool info
MuckT May 7, 2024
b6d6d6c
chore add upward graph icon
MuckT May 7, 2024
292f124
feat: add earn active pool component
MuckT May 7, 2024
a4d80e6
feat: conditionally use earn active pool component in tab discover
MuckT May 7, 2024
ec32e24
style: remove export from icon props
MuckT May 8, 2024
c9885ef
fix: conditional rendering of earn cta and active pools
MuckT May 8, 2024
805df24
test: display for earn active pool and earn cta on tab discover
MuckT May 8, 2024
e5644f6
test: remove unneeded async
MuckT May 8, 2024
9873c6b
chore: add aave arb usdc token id
MuckT May 8, 2024
f79cee8
chore: use address from network config instead of contract
MuckT May 8, 2024
b7c5372
Merge branch 'tomm/act-1179-0' into tomm/act-1179-2
MuckT May 8, 2024
cc8bfc3
style: rename tokenAddress assetAddress
MuckT May 8, 2024
6ac6e2a
test: remove mock contract read
MuckT May 8, 2024
5f2b208
Merge branch 'tomm/act-1179-0' into tomm/act-1179-2
MuckT May 8, 2024
c78a39b
chore: remove unneeded fetchAavePoolUserBalance function
MuckT May 8, 2024
a11a73d
chore: remove unneeded viem mock
MuckT May 8, 2024
6516cc8
test: rename test spec
MuckT May 8, 2024
06aa754
Merge branch 'tomm/act-1179-0' into tomm/act-1179-2
MuckT May 8, 2024
c606d09
feat: use token info from address metadata
MuckT May 9, 2024
532eac8
feat: move conditional rendering to a hook
MuckT May 9, 2024
fa05f35
fix: replace hook with possibly null address with use async hook
MuckT May 9, 2024
29f0c0d
Merge branch 'main' into tomm/act-1179-2
MuckT May 9, 2024
f927cf8
feat: abstract fetchAavePoolInfo to handle multiple pools
MuckT May 9, 2024
a4575be
feat: add token details variant buttons in EarnActivePool component
MuckT May 9, 2024
dd2deb9
feat: update useEarnCard hook to handle multiple pools
MuckT May 9, 2024
f5b7c8e
chore: use is address type guard from viem
MuckT May 9, 2024
d35890e
Merge branch 'tomm/act-1179-2' into tomm/act-1185
MuckT May 9, 2024
172d3ad
Merge branch 'main' into tomm/act-1179-2
MuckT May 9, 2024
4f339e7
style: use components instead of a hook that returns components
MuckT May 9, 2024
dbfdb4f
Merge branch 'tomm/act-1179-2' into tomm/act-1185
MuckT May 9, 2024
1d03718
Merge branch 'main' into tomm/act-1185
MuckT May 9, 2024
afec428
test: aave arbiturm usdc renders when user has balance
MuckT May 10, 2024
f9cc578
chore: infer network id from deposit token
MuckT May 13, 2024
8048d97
style: rename buttonDisplay to cta
MuckT May 13, 2024
27c08ed
style: use onError and get network from networkId directly
MuckT May 13, 2024
05dc7f6
fix: prevent render of zero in text element
MuckT May 13, 2024
92487c6
style: on return true for target gate
MuckT May 13, 2024
4b31a0a
style: simplify conditional render
MuckT May 13, 2024
7f6a9dd
fix: remove optional chaining
MuckT May 13, 2024
483b32d
chore: use new button styles
MuckT May 13, 2024
03b7636
chore: add analytics event for view pools press
MuckT May 13, 2024
1e73a5d
test: add earn active pool tests for cta variants
MuckT May 13, 2024
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
3 changes: 2 additions & 1 deletion locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2356,7 +2356,8 @@
"totalValue": "Total Value:",
"apy": "~{{apy}}% APY",
"exitPool": "Exit Pool",
"depositMore": "Deposit More"
"depositMore": "Deposit More",
"viewPools": "View Pools"
},
"depositBottomSheet": {
"title": "Review & Deposit",
Expand Down
1 change: 1 addition & 0 deletions src/analytics/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -682,4 +682,5 @@ export enum EarnEvents {
earn_deposit_terms_and_conditions_press = 'earn_deposit_terms_and_conditions_press',
earn_deposit_complete = 'earn_deposit_complete',
earn_deposit_cancel = 'earn_deposit_cancel',
earn_view_pools_press = 'earn_view_pools_press',
}
1 change: 1 addition & 0 deletions src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,7 @@ interface EarnEventsProperties {
[EarnEvents.earn_deposit_terms_and_conditions_press]: undefined
[EarnEvents.earn_deposit_complete]: undefined
[EarnEvents.earn_deposit_cancel]: undefined
[EarnEvents.earn_view_pools_press]: undefined
}

export type AnalyticsPropertiesList = AppEventsProperties &
Expand Down
1 change: 1 addition & 0 deletions src/analytics/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
[EarnEvents.earn_deposit_terms_and_conditions_press]: `When a user taps on the terms and conditions link on the deposit bottom sheet`,
[EarnEvents.earn_deposit_complete]: `When a user taps on the complete button on the deposit bottom sheet`,
[EarnEvents.earn_deposit_cancel]: `When a user taps on the cancel button on the deposit bottom sheet`,
[EarnEvents.earn_view_pools_press]: `When the user taps on the view pools button from token details`,

// Legacy event docs
// The below events had docs, but are no longer produced by the latest app version.
Expand Down
11 changes: 6 additions & 5 deletions src/dappsExplorer/TabDiscover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import NoResults from 'src/dappsExplorer/NoResults'
import { searchDappList } from 'src/dappsExplorer/searchDappList'
import useDappFavoritedToast from 'src/dappsExplorer/useDappFavoritedToast'
import useOpenDapp from 'src/dappsExplorer/useOpenDapp'
import useEarn from 'src/earn/useEarn'
import { EarnCardDiscover } from 'src/earn/EarnCard'
import { currentLanguageSelector } from 'src/i18n/selectors'
import { Screens } from 'src/navigator/Screens'
import useScrollAwareHeader from 'src/navigator/ScrollAwareHeader'
Expand All @@ -43,6 +43,7 @@ import { useDispatch, useSelector } from 'src/redux/hooks'
import { Colors } from 'src/styles/colors'
import fontStyles, { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import networkConfig from 'src/web3/networkConfig'

const AnimatedSectionList =
Animated.createAnimatedComponent<SectionListProps<Dapp, SectionData>>(SectionList)
Expand Down Expand Up @@ -73,9 +74,6 @@ function TabDiscover({ navigation }: Props) {
const nonFavoriteDappsWithCategoryNames = useSelector(nonFavoriteDappsWithCategoryNamesSelector)
const favoriteDappsWithCategoryNames = useSelector(favoriteDappsWithCategoryNamesSelector)

// Earning Pool Aave
const { Earn } = useEarn()

const [filterChips, setFilterChips] = useState<BooleanFilterChip<DappWithCategoryNames>[]>(() =>
categories.map((category) => ({
id: category.id,
Expand Down Expand Up @@ -237,7 +235,10 @@ function TabDiscover({ navigation }: Props) {
</Text>
}
<DappFeaturedActions onPressShowDappRankings={handleShowDappRankings} />
<Earn />
<EarnCardDiscover
poolTokenId={networkConfig.aaveArbUsdcTokenId}
depositTokenId={networkConfig.arbUsdcTokenId}
/>
<SearchInput
onChangeText={(text) => {
setSearchTerm(text)
Expand Down
79 changes: 79 additions & 0 deletions src/earn/EarnActivePool.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { fireEvent, render } from '@testing-library/react-native'
import React from 'react'
import { Provider } from 'react-redux'
import { EarnEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import EarnActivePool from 'src/earn/EarnActivePool'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { NetworkId } from 'src/transactions/types'
import networkConfig from 'src/web3/networkConfig'
import { createMockStore } from 'test/utils'
import { mockAaveArbUsdcAddress } from 'test/values'

const store = createMockStore({
tokens: {
tokenBalances: {
[networkConfig.aaveArbUsdcTokenId]: {
networkId: NetworkId['arbitrum-sepolia'],
address: mockAaveArbUsdcAddress,
tokenId: networkConfig.aaveArbUsdcTokenId,
symbol: 'aArbSepUSDC',
priceUsd: '1',
balance: '10.75',
priceFetchedAt: Date.now(),
},
},
},
})

describe('EarnActivePool', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('should render correctly with ExitAndDeposit cta', () => {
const { getByText } = render(
<Provider store={store}>
<EarnActivePool
cta="ExitAndDeposit"
depositTokenId={networkConfig.arbUsdcTokenId}
poolTokenId={networkConfig.aaveArbUsdcTokenId}
/>
</Provider>
)

expect(getByText('earnFlow.activePools.exitPool')).toBeTruthy()
expect(getByText('earnFlow.activePools.depositMore')).toBeTruthy()
})

it('should render correctly with ViewPools cta', () => {
const { getByText } = render(
<Provider store={store}>
<EarnActivePool
cta="ViewPools"
depositTokenId={networkConfig.arbUsdcTokenId}
poolTokenId={networkConfig.aaveArbUsdcTokenId}
/>
</Provider>
)

expect(getByText('earnFlow.activePools.viewPools')).toBeTruthy()
})

it('should have correct analytics and navigation with ViewPools cta', () => {
const { getByText } = render(
<Provider store={store}>
<EarnActivePool
cta="ViewPools"
depositTokenId={networkConfig.arbUsdcTokenId}
poolTokenId={networkConfig.aaveArbUsdcTokenId}
/>
</Provider>
)

fireEvent.press(getByText('earnFlow.activePools.viewPools'))
expect(ValoraAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_view_pools_press)
expect(navigate).toBeCalledWith(Screens.TabDiscover)
})
})
132 changes: 82 additions & 50 deletions src/earn/EarnActivePool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
import { useAsync } from 'react-async-hook'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { EarnEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import Button, { BtnSizes, BtnTypes } from 'src/components/Button'
import SkeletonPlaceholder from 'src/components/SkeletonPlaceholder'
import TokenDisplay from 'src/components/TokenDisplay'
import { fetchAavePoolInfo } from 'src/earn/poolInfo'
import UpwardGraph from 'src/icons/UpwardGraph'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import Colors from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import { useTokenInfo } from 'src/tokens/hooks'
import Logger from 'src/utils/Logger'
import networkConfig from 'src/web3/networkConfig'
import networkConfig, { networkIdToNetwork } from 'src/web3/networkConfig'
import { isAddress } from 'viem'

const TAG = 'earn/EarnActivePool'
Expand All @@ -33,27 +37,39 @@
)
}

export default function EarnActivePool() {
interface Props {
cta: 'ExitAndDeposit' | 'ViewPools'
depositTokenId: string
poolTokenId: string
}

export default function EarnActivePool({ depositTokenId, poolTokenId, cta }: Props) {
const { t } = useTranslation()
const token = useTokenInfo(networkConfig.arbUsdcTokenId)
const poolToken = useTokenInfo(networkConfig.aaveArbUsdcTokenId)
const asyncPoolInfo = useAsync(async () => {
if (!token || !token.address) {
// should never happen
Logger.warn(TAG, `Token with id ${networkConfig.arbUsdcTokenId} not found`)
throw new Error(`Token with id ${networkConfig.arbUsdcTokenId} not found`)
}
if (!isAddress(token.address)) {
Logger.warn(
TAG,
`Token with id ${networkConfig.arbUsdcTokenId} does not contain a valid address`
)
throw new Error(
`Token with id ${networkConfig.arbUsdcTokenId} does not contain a valid address`
)
const depositToken = useTokenInfo(depositTokenId)
const poolToken = useTokenInfo(poolTokenId)
const asyncPoolInfo = useAsync(
async () => {
if (!depositToken || !depositToken.address) {
throw new Error(`Token with id ${depositTokenId} not found`)
}

if (!isAddress(depositToken.address)) {
throw new Error(`Token with id ${depositTokenId} does not contain a valid address`)

Check warning on line 57 in src/earn/EarnActivePool.tsx

View check run for this annotation

Codecov / codecov/patch

src/earn/EarnActivePool.tsx#L57

Added line #L57 was not covered by tests
}
satish-ravi marked this conversation as resolved.
Show resolved Hide resolved

return fetchAavePoolInfo({

Check warning on line 60 in src/earn/EarnActivePool.tsx

View check run for this annotation

Codecov / codecov/patch

src/earn/EarnActivePool.tsx#L60

Added line #L60 was not covered by tests
assetAddress: depositToken.address,
contractAddress: networkConfig.arbAavePoolV3ContractAddress,
network: networkIdToNetwork[depositToken.networkId],
})
},
[],
{
onError: (error) => {
Logger.warn(TAG, error.message)
},
}
return fetchAavePoolInfo(token.address)
}, [])
)

return (
<View style={styles.card} testID="EarnActivePool">
Expand All @@ -66,19 +82,19 @@
<TokenDisplay
amount={poolToken.balance}
showLocalAmount={false}
testID={`${networkConfig.arbUsdcTokenId}-totalAmount`}
tokenId={networkConfig.arbUsdcTokenId}
testID={`${depositTokenId}-totalAmount`}
tokenId={depositTokenId}
/>
)}
</View>
<View style={styles.row}>
{asyncPoolInfo.error && <View />}
{asyncPoolInfo.loading && <PoolDetailsLoading />}
{asyncPoolInfo.result && (
{asyncPoolInfo.result && !!asyncPoolInfo.result.apy && (
<View style={styles.apyContainer}>
<Text style={styles.apyText}>
{t('earnFlow.activePools.apy', {
apy: (asyncPoolInfo.result.apy * 100).toFixed(2) ?? '',
apy: (asyncPoolInfo.result.apy * 100).toFixed(2),
})}
</Text>
<UpwardGraph />
Expand All @@ -88,36 +104,52 @@
<TokenDisplay
amount={poolToken.balance}
showLocalAmount={true}
testID={`${networkConfig.arbUsdcTokenId}-totalAmount`}
tokenId={networkConfig.arbUsdcTokenId}
testID={`${depositTokenId}-totalAmount`}
tokenId={depositTokenId}
/>
)}
</View>
</View>
<View style={styles.buttonContainer}>
<Button
onPress={() => {
// TODO (act-1180) create earn review withdraw screen
// Will navigate to this screen with appropriate props
// fire analytics
}}
text={t('earnFlow.activePools.exitPool')}
type={BtnTypes.SECONDARY}
size={BtnSizes.FULL}
style={styles.button}
/>
<Button
onPress={() => {
// TODO (act-1176) create earn enter amount screen
// Will navigate to this screen with appropriate props
// fire analytics
}}
text={t('earnFlow.activePools.depositMore')}
type={BtnTypes.PRIMARY}
size={BtnSizes.FULL}
style={styles.button}
/>
</View>
{cta === 'ExitAndDeposit' && (
<View style={styles.buttonContainer}>
<Button
onPress={() => {

Check warning on line 116 in src/earn/EarnActivePool.tsx

View check run for this annotation

Codecov / codecov/patch

src/earn/EarnActivePool.tsx#L116

Added line #L116 was not covered by tests
// TODO (act-1180) create earn review withdraw screen
// Will navigate to this screen with appropriate props
// fire analytics
}}
text={t('earnFlow.activePools.exitPool')}
type={BtnTypes.GRAY_WITH_BORDER}
size={BtnSizes.FULL}
style={styles.button}
/>
<Button
onPress={() => {

Check warning on line 127 in src/earn/EarnActivePool.tsx

View check run for this annotation

Codecov / codecov/patch

src/earn/EarnActivePool.tsx#L127

Added line #L127 was not covered by tests
// TODO (act-1176) create earn enter amount screen
// Will navigate to this screen with appropriate props
// fire analytics
}}
text={t('earnFlow.activePools.depositMore')}
type={BtnTypes.PRIMARY}
size={BtnSizes.FULL}
style={styles.button}
/>
</View>
)}
{cta === 'ViewPools' && (
<View style={styles.buttonContainer}>
<Button
onPress={() => {
ValoraAnalytics.track(EarnEvents.earn_view_pools_press)
navigate(Screens.TabDiscover)
Copy link
Contributor

Choose a reason for hiding this comment

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

can we add an analytics event here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added in 03b7636 and tested in 1e73a5d!

}}
text={t('earnFlow.activePools.viewPools')}
type={BtnTypes.GRAY_WITH_BORDER}
size={BtnSizes.FULL}
style={styles.button}
/>
</View>
)}
</View>
</View>
)
Expand Down
46 changes: 46 additions & 0 deletions src/earn/EarnCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import { View } from 'react-native'
import ItemSeparator from 'src/components/ItemSeparator'
import EarnActivePool from 'src/earn/EarnActivePool'
import EarnCta from 'src/earn/EarnCta'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import { Spacing } from 'src/styles/styles'
import { useTokenInfo } from 'src/tokens/hooks'

interface Props {
depositTokenId: string
poolTokenId: string
}

export function EarnCardDiscover({ depositTokenId, poolTokenId }: Props) {
const showStablecoinEarn = getFeatureGate(StatsigFeatureGates.SHOW_STABLECOIN_EARN)
const poolToken = useTokenInfo(poolTokenId)

if (showStablecoinEarn) {
return poolToken && poolToken.balance.gt(0) ? (
<EarnActivePool
cta="ExitAndDeposit"
depositTokenId={depositTokenId}
poolTokenId={poolTokenId}
/>
) : (
<EarnCta />
)
}
return null
}

export function EarnCardTokenDetails({ depositTokenId, poolTokenId }: Props) {
const showStablecoinEarn = getFeatureGate(StatsigFeatureGates.SHOW_STABLECOIN_EARN)
const poolToken = useTokenInfo(poolTokenId)

return showStablecoinEarn && poolToken && poolToken.balance.gt(0) ? (
<>
<ItemSeparator />
<View style={{ margin: Spacing.Regular16 }}>
<EarnActivePool cta="ViewPools" depositTokenId={depositTokenId} poolTokenId={poolTokenId} />
</View>
</>
) : null

Check warning on line 45 in src/earn/EarnCard.tsx

View check run for this annotation

Codecov / codecov/patch

src/earn/EarnCard.tsx#L45

Added line #L45 was not covered by tests
}
6 changes: 5 additions & 1 deletion src/earn/poolInfo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ describe('poolInfo', () => {
currentLiquidityRate: BigInt(1e27 * 0.036),
})

const result = await fetchAavePoolInfo('0x1234')
const result = await fetchAavePoolInfo({
assetAddress: '0x1234',
contractAddress: networkConfig.arbAavePoolV3ContractAddress,
network: Network.Arbitrum,
})

expect(result).toEqual({ apy: 0.0366558430938988 })
expect(publicClient[Network.Arbitrum].readContract).toHaveBeenCalledWith({
Expand Down
Loading
Loading