-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(feedV2): generic claim reward type (#6198)
### Description Introduce new generic type for the TX feed for claim reward. `CLAIM_REWARD` replaces `EARN_CLAIM_REWARD` Why? 1. This allows directly supporting more claim reward TXs, not only for Earn but also GoodDollar, etc. 2. We can do this now without too much effort and keep a "simple" backend implementation for the new `getWalletTransactions` Similar to what was done in #6189 This is just for the new feed API with Zerion. ### Test plan - Tests pass ### Related issues - Related to RET-1204 - Also see this Slack [thread](https://valora-app.slack.com/archives/C029Z1QMD7B/p1729847488933829) for context ### 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
1 parent
ef81adc
commit 06c96c3
Showing
12 changed files
with
692 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import { fireEvent, render, within } from '@testing-library/react-native' | ||
import React from 'react' | ||
import { Provider } from 'react-redux' | ||
import AppAnalytics from 'src/analytics/AppAnalytics' | ||
import { HomeEvents } from 'src/analytics/Events' | ||
import { navigate } from 'src/navigator/NavigationService' | ||
import { Screens } from 'src/navigator/Screens' | ||
import { getFeatureGate } from 'src/statsig' | ||
import { StatsigFeatureGates } from 'src/statsig/types' | ||
import ClaimRewardFeedItem from 'src/transactions/feed/ClaimRewardFeedItem' | ||
import { NetworkId } from 'src/transactions/types' | ||
import { createMockStore } from 'test/utils' | ||
import { | ||
mockAaveArbUsdcAddress, | ||
mockAaveArbUsdcTokenId, | ||
mockArbArbTokenId, | ||
mockArbUsdcTokenId, | ||
mockClaimRewardTransaction, | ||
} from 'test/values' | ||
|
||
jest.mock('src/statsig') | ||
jest | ||
.mocked(getFeatureGate) | ||
.mockImplementation((featureGateName) => featureGateName === StatsigFeatureGates.SHOW_POSITIONS) | ||
|
||
const store = createMockStore({ | ||
tokens: { | ||
tokenBalances: { | ||
[mockArbUsdcTokenId]: { | ||
tokenId: mockArbUsdcTokenId, | ||
symbol: 'USDC', | ||
priceUsd: '1', | ||
priceFetchedAt: Date.now(), | ||
networkId: NetworkId['arbitrum-sepolia'], | ||
}, | ||
[mockArbArbTokenId]: { | ||
tokenId: mockArbArbTokenId, | ||
symbol: 'ARB', | ||
priceUsd: '0.9898', | ||
priceFetchedAt: Date.now(), | ||
networkId: NetworkId['arbitrum-sepolia'], | ||
}, | ||
[mockAaveArbUsdcTokenId]: { | ||
networkId: NetworkId['arbitrum-sepolia'], | ||
address: mockAaveArbUsdcAddress, | ||
tokenId: mockAaveArbUsdcTokenId, | ||
symbol: 'aArbSepUSDC', | ||
priceUsd: '1', | ||
priceFetchedAt: Date.now(), | ||
}, | ||
}, | ||
}, | ||
positions: { | ||
positions: [ | ||
{ | ||
type: 'app-token', | ||
networkId: NetworkId['arbitrum-sepolia'], | ||
address: '0x460b97bd498e1157530aeb3086301d5225b91216', | ||
tokenId: 'arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216', | ||
positionId: 'arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216', | ||
appId: 'aave', | ||
appName: 'Aave', | ||
symbol: 'aArbSepUSDC', | ||
decimals: 6, | ||
displayProps: { | ||
title: 'USDC', | ||
description: 'Supplied (APY: 1.92%)', | ||
imageUrl: 'https://raw.githubusercontent.com/valora-inc/dapp-list/main/assets/aave.png', | ||
}, | ||
dataProps: { | ||
yieldRates: [ | ||
{ | ||
percentage: 1.9194202601763743, | ||
label: 'Earnings APY', | ||
tokenId: 'arbitrum-sepolia:0x75faf114eafb1bdbe2f0316df893fd58ce46aa4d', | ||
}, | ||
], | ||
earningItems: [], | ||
depositTokenId: 'arbitrum-sepolia:0x75faf114eafb1bdbe2f0316df893fd58ce46aa4d', | ||
withdrawTokenId: 'arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216', | ||
}, | ||
tokens: [ | ||
{ | ||
tokenId: 'arbitrum-sepolia:0x75faf114eafb1bdbe2f0316df893fd58ce46aa4d', | ||
networkId: NetworkId['arbitrum-sepolia'], | ||
address: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d', | ||
symbol: 'USDC', | ||
decimals: 6, | ||
priceUsd: '0', | ||
type: 'base-token', | ||
balance: '0', | ||
}, | ||
], | ||
pricePerShare: ['1'], | ||
priceUsd: '0.999', | ||
balance: '10', | ||
supply: '190288.768509', | ||
availableShortcutIds: ['deposit', 'withdraw'], | ||
}, | ||
], | ||
earnPositionIds: ['arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216'], | ||
}, | ||
}) | ||
|
||
describe('ClaimRewardFeedItem', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks() | ||
}) | ||
|
||
it('should render correctly', () => { | ||
const { getByText, getByTestId } = render( | ||
<Provider store={store}> | ||
<ClaimRewardFeedItem transaction={mockClaimRewardTransaction} /> | ||
</Provider> | ||
) | ||
|
||
expect(getByText('transactionFeed.claimRewardTitle')).toBeTruthy() | ||
expect(getByText('transactionFeed.claimRewardSubtitle, {"txAppName":"Aave"}')).toBeTruthy() | ||
expect( | ||
within(getByTestId('ClaimRewardFeedItem/amount-crypto')).getByText('+1.50 ARB') | ||
).toBeTruthy() | ||
expect(within(getByTestId('ClaimRewardFeedItem/amount-local')).getByText('₱1.97')).toBeTruthy() | ||
}) | ||
|
||
it('should display when app name is not available', () => { | ||
const { getByText } = render( | ||
<Provider store={store}> | ||
<ClaimRewardFeedItem transaction={{ ...mockClaimRewardTransaction, appName: undefined }} /> | ||
</Provider> | ||
) | ||
|
||
expect(getByText('transactionFeed.claimRewardSubtitle, {"context":"noTxAppName"}')).toBeTruthy() | ||
}) | ||
|
||
it('should navigate correctly on tap', () => { | ||
const { getByTestId } = render( | ||
<Provider store={store}> | ||
<ClaimRewardFeedItem transaction={mockClaimRewardTransaction} /> | ||
</Provider> | ||
) | ||
|
||
fireEvent.press( | ||
getByTestId(`ClaimRewardFeedItem/${mockClaimRewardTransaction.transactionHash}`) | ||
) | ||
expect(navigate).toHaveBeenCalledWith(Screens.TransactionDetailsScreen, { | ||
transaction: mockClaimRewardTransaction, | ||
}) | ||
}) | ||
|
||
it('should fire analytic event on tap', () => { | ||
const { getByTestId } = render( | ||
<Provider store={store}> | ||
<ClaimRewardFeedItem transaction={mockClaimRewardTransaction} /> | ||
</Provider> | ||
) | ||
|
||
fireEvent.press( | ||
getByTestId(`ClaimRewardFeedItem/${mockClaimRewardTransaction.transactionHash}`) | ||
) | ||
expect(AppAnalytics.track).toHaveBeenCalledWith(HomeEvents.transaction_feed_item_select) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import BigNumber from 'bignumber.js' | ||
import React from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import { StyleSheet, Text, View } from 'react-native' | ||
import AppAnalytics from 'src/analytics/AppAnalytics' | ||
import { HomeEvents } from 'src/analytics/Events' | ||
import TokenDisplay from 'src/components/TokenDisplay' | ||
import Touchable from 'src/components/Touchable' | ||
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 variables from 'src/styles/variables' | ||
import TransactionFeedItemImage from 'src/transactions/feed/TransactionFeedItemImage' | ||
import { ClaimReward } from 'src/transactions/types' | ||
|
||
interface DescriptionProps { | ||
transaction: ClaimReward | ||
} | ||
|
||
function Description({ transaction }: DescriptionProps) { | ||
const { t } = useTranslation() | ||
const txAppName = transaction.appName | ||
const title = t('transactionFeed.claimRewardTitle') | ||
const subtitle = t('transactionFeed.claimRewardSubtitle', { | ||
context: !txAppName ? 'noTxAppName' : undefined, | ||
txAppName, | ||
}) | ||
|
||
return ( | ||
<View style={styles.contentContainer}> | ||
<Text style={styles.title} testID={'ClaimRewardFeedItem/title'} numberOfLines={1}> | ||
{title} | ||
</Text> | ||
<Text style={styles.subtitle} testID={'ClaimRewardFeedItem/subtitle'} numberOfLines={1}> | ||
{subtitle} | ||
</Text> | ||
</View> | ||
) | ||
} | ||
|
||
interface AmountDisplayProps { | ||
transaction: ClaimReward | ||
isLocal: boolean | ||
} | ||
|
||
function AmountDisplay({ transaction, isLocal }: AmountDisplayProps) { | ||
const amountValue = new BigNumber(transaction.amount.value) | ||
const tokenId = transaction.amount.tokenId | ||
|
||
const textStyle = isLocal ? styles.amountSubtitle : [styles.amountTitle, { color: Colors.accent }] | ||
|
||
return ( | ||
<TokenDisplay | ||
amount={amountValue} | ||
localAmount={transaction.amount.localAmount} | ||
tokenId={tokenId} | ||
showLocalAmount={isLocal} | ||
showSymbol={true} | ||
showExplicitPositiveSign={!isLocal} | ||
hideSign={!!isLocal} | ||
style={textStyle} | ||
testID={`ClaimRewardFeedItem/amount-${isLocal ? 'local' : 'crypto'}`} | ||
/> | ||
) | ||
} | ||
|
||
interface AmountProps { | ||
transaction: ClaimReward | ||
} | ||
|
||
function Amount({ transaction }: AmountProps) { | ||
return ( | ||
<View style={styles.amountContainer}> | ||
<AmountDisplay transaction={transaction} isLocal={false} /> | ||
<AmountDisplay transaction={transaction} isLocal={true} /> | ||
</View> | ||
) | ||
} | ||
|
||
interface Props { | ||
transaction: ClaimReward | ||
} | ||
|
||
export default function ClaimRewardFeedItem({ transaction }: Props) { | ||
return ( | ||
<Touchable | ||
testID={`ClaimRewardFeedItem/${transaction.transactionHash}`} | ||
onPress={() => { | ||
// TODO: we'll add the type in a subsequent PR | ||
AppAnalytics.track(HomeEvents.transaction_feed_item_select) | ||
navigate(Screens.TransactionDetailsScreen, { transaction }) | ||
}} | ||
> | ||
<View style={styles.container}> | ||
<TransactionFeedItemImage | ||
status={transaction.status} | ||
transactionType={transaction.type} | ||
networkId={transaction.networkId} | ||
/> | ||
<Description transaction={transaction} /> | ||
<Amount transaction={transaction} /> | ||
</View> | ||
</Touchable> | ||
) | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
flexDirection: 'row', | ||
paddingVertical: Spacing.Small12, | ||
paddingHorizontal: variables.contentPadding, | ||
alignItems: 'center', | ||
}, | ||
contentContainer: { | ||
flex: 1, | ||
paddingHorizontal: variables.contentPadding, | ||
}, | ||
title: { | ||
...typeScale.labelMedium, | ||
color: Colors.black, | ||
}, | ||
subtitle: { | ||
...typeScale.bodySmall, | ||
color: Colors.gray4, | ||
}, | ||
amountContainer: { | ||
maxWidth: '50%', | ||
}, | ||
amountTitle: { | ||
...typeScale.labelMedium, | ||
color: Colors.black, | ||
flexWrap: 'wrap', | ||
textAlign: 'right', | ||
}, | ||
amountSubtitle: { | ||
...typeScale.bodySmall, | ||
color: Colors.gray4, | ||
flexWrap: 'wrap', | ||
textAlign: 'right', | ||
}, | ||
}) |
Oops, something went wrong.