Skip to content

Commit

Permalink
feat(feedV2): generic deposit and withdraw types (#6189)
Browse files Browse the repository at this point in the history
### Description

Introduce new generic types for the TX feed for deposits and
withdrawals.

`DEPOSIT` replaces `EARN_DEPOSIT` and `EARN_SWAP_DEPOSIT`.
`WITHDRAW` replaces `EARN_WITHDRAW`.

Why?

1. This allows directly supporting all the withdrawals and deposits for
all dapps that Zerion already knows about.
2. We can do this now without too much effort and keep a "simple"
backend implementation for the new `getWalletTransactions`

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
jeanregisser authored Oct 29, 2024
1 parent 811e881 commit 6581faf
Show file tree
Hide file tree
Showing 11 changed files with 858 additions and 1 deletion.
22 changes: 21 additions & 1 deletion locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2282,7 +2282,27 @@
"infiniteApprovalDescription": "Approved infinite {{tokenSymbol}} for trade",
"revokeApprovalDescription": "Revoked {{tokenSymbol}} for trade",
"descriptionLabel": "Details",
"crossChainSwapTransactionLabel": "Cross-chain"
"crossChainSwapTransactionLabel": "Cross-chain",
"depositTitle": "Deposited",
"depositSubtitle": "to {{txAppName}} Pool",
"depositSubtitle_noTxAppName": "to unknown pool",
"withdrawTitle": "Withdrew",
"withdrawSubtitle": "from {{txAppName}} Pool",
"withdrawSubtitle_noTxAppName": "from unknown app"
},
"transactionDetails": {
"descriptionLabel": "Details",
"depositTitle": "Deposited",
"depositSubtitle": "Deposited {{tokenSymbol}} to {{txAppName}} Pool",
"depositSubtitle_noTxAppName": "Deposited {{tokenSymbol}} to unknown app",
"depositDetails": "Amount Deposited",
"withdrawTitle": "Withdrew",
"withdrawSubtitle": "Withdrew {{tokenSymbol}} from {{txAppName}} Pool",
"withdrawSubtitle_noTxAppName": "Withdrew {{tokenSymbol}} from unknown app",
"withdrawDetails": "Amount Withdrawn",
"swap": "Swap",
"network": "Network",
"fees": "Fees"
},
"multichainBeta": {
"beta": "BETA",
Expand Down
86 changes: 86 additions & 0 deletions src/transactions/feed/DepositOrWithdrawFeedItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { render } from '@testing-library/react-native'
import * as React from 'react'
import { Provider } from 'react-redux'
import DepositOrWithdrawFeedItem from 'src/transactions/feed/DepositOrWithdrawFeedItem'
import { NetworkId, TokenTransactionTypeV2, TransactionStatus } from 'src/transactions/types'
import { createMockStore } from 'test/utils'
import { mockCeloTokenId, mockCusdTokenId } from 'test/values'

describe('DepositOrWithdrawFeedItem', () => {
const store = createMockStore()

const depositTransaction = {
type: TokenTransactionTypeV2.Deposit as const,
networkId: NetworkId['celo-mainnet'],
timestamp: 1234567890,
block: '123456',
transactionHash: '0x123',
fees: [],
appName: 'Some Dapp',
inAmount: {
value: '100',
tokenId: mockCeloTokenId,
},
outAmount: {
value: '100',
tokenId: mockCusdTokenId,
},
status: TransactionStatus.Complete,
}

const withdrawTransaction = {
...depositTransaction,
type: TokenTransactionTypeV2.Withdraw as const,
}

it('renders deposit correctly', () => {
const { getByText, getByTestId } = render(
<Provider store={store}>
<DepositOrWithdrawFeedItem transaction={depositTransaction} />
</Provider>
)

expect(getByText('transactionFeed.depositTitle')).toBeTruthy()
expect(getByText('transactionFeed.depositSubtitle, {"txAppName":"Some Dapp"}')).toBeTruthy()
expect(getByTestId('DepositOrWithdrawFeedItem/DEPOSIT-amount-crypto')).toBeTruthy()
expect(getByTestId('DepositOrWithdrawFeedItem/DEPOSIT-amount-local')).toBeTruthy()
})

it('renders withdraw correctly', () => {
const { getByText, getByTestId } = render(
<Provider store={store}>
<DepositOrWithdrawFeedItem transaction={withdrawTransaction} />
</Provider>
)

expect(getByText('transactionFeed.withdrawTitle')).toBeTruthy()
expect(getByText('transactionFeed.withdrawSubtitle, {"txAppName":"Some Dapp"}')).toBeTruthy()
expect(getByTestId('DepositOrWithdrawFeedItem/WITHDRAW-amount-crypto')).toBeTruthy()
expect(getByTestId('DepositOrWithdrawFeedItem/WITHDRAW-amount-local')).toBeTruthy()
})

it('displays app name when available', () => {
const { getByText } = render(
<Provider store={store}>
<DepositOrWithdrawFeedItem transaction={depositTransaction} />
</Provider>
)

expect(getByText('transactionFeed.depositSubtitle, {"txAppName":"Some Dapp"}')).toBeTruthy()
})

it('does not display app name when not available', () => {
const transactionWithoutProvider = {
...depositTransaction,
appName: undefined,
}

const { queryByText } = render(
<Provider store={store}>
<DepositOrWithdrawFeedItem transaction={transactionWithoutProvider} />
</Provider>
)

expect(queryByText('transactionFeed.depositSubtitle, {"txAppName":"Some Dapp"}')).toBeNull()
})
})
178 changes: 178 additions & 0 deletions src/transactions/feed/DepositOrWithdrawFeedItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
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 { DepositOrWithdraw, TokenTransactionTypeV2 } from 'src/transactions/types'

interface DescriptionProps {
transaction: DepositOrWithdraw
}

function Description({ transaction }: DescriptionProps) {
const { t } = useTranslation()
const txAppName = transaction.appName
let title
let subtitle

switch (transaction.type) {
case TokenTransactionTypeV2.Deposit:
title = t('transactionFeed.depositTitle')
subtitle = t('transactionFeed.depositSubtitle', {
context: !txAppName ? 'noTxAppName' : undefined,
txAppName,
})
break
case TokenTransactionTypeV2.Withdraw:
title = t('transactionFeed.withdrawTitle')
subtitle = t('transactionFeed.withdrawSubtitle', {
context: !txAppName ? 'noTxAppName' : undefined,
txAppName,
})
break
}

return (
<View style={styles.contentContainer}>
<Text style={styles.title} testID={'DepositOrWithdrawFeedItem/title'} numberOfLines={1}>
{title}
</Text>
{!!txAppName && (
<Text
style={styles.subtitle}
testID={'DepositOrWithdrawFeedItem/subtitle'}
numberOfLines={1}
>
{subtitle}
</Text>
)}
</View>
)
}

interface AmountDisplayProps {
transaction: DepositOrWithdraw
isLocal: boolean
}

function AmountDisplay({ transaction, isLocal }: AmountDisplayProps) {
let amountValue
let tokenId

switch (transaction.type) {
case TokenTransactionTypeV2.Deposit:
amountValue = new BigNumber(-transaction.outAmount.value)
tokenId = transaction.outAmount.tokenId
break
case TokenTransactionTypeV2.Withdraw:
amountValue = new BigNumber(transaction.inAmount.value)
tokenId = transaction.inAmount.tokenId
break
}

const textStyle = isLocal
? styles.amountSubtitle
: [
styles.amountTitle,
transaction.type === TokenTransactionTypeV2.Withdraw && { color: Colors.accent },
]

return (
<TokenDisplay
amount={amountValue}
tokenId={tokenId}
showLocalAmount={isLocal}
showSymbol={true}
showExplicitPositiveSign={!isLocal}
hideSign={!!isLocal}
style={textStyle}
testID={`DepositOrWithdrawFeedItem/${transaction.type}-amount-${isLocal ? 'local' : 'crypto'}`}
/>
)
}

interface AmountProps {
transaction: DepositOrWithdraw
}

function Amount({ transaction }: AmountProps) {
return (
<View style={styles.amountContainer}>
<AmountDisplay transaction={transaction} isLocal={false} />
<AmountDisplay transaction={transaction} isLocal={true} />
</View>
)
}

interface Props {
transaction: DepositOrWithdraw
}

export default function DepositOrWithdrawFeedItem({ transaction }: Props) {
return (
<Touchable
testID={`DepositOrWithdrawFeedItem/${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',
},
})
Loading

0 comments on commit 6581faf

Please sign in to comment.