Skip to content

Commit

Permalink
feat(earn): support cross chain deposit in tx feed (#6431)
Browse files Browse the repository at this point in the history
### Description

Adds the UI for cross chain deposit and updates the pending tx to use
cross chain deposit. Analytics / confirming tx at the right time will be
in a follow up

### Test plan



https://github.com/user-attachments/assets/8a01d876-f70d-45a7-a8ce-b3828314c3c7



https://github.com/user-attachments/assets/6995453e-31e1-4bbc-8ff1-3be0223d3eaf



### Related issues

- Part of ACT-1514

### 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
satish-ravi authored Jan 22, 2025
1 parent 9a70651 commit 299072a
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 98 deletions.
30 changes: 7 additions & 23 deletions src/earn/saga.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,18 +187,13 @@ describe('depositSubmitSaga', () => {
tokenId: mockArbUsdcTokenId,
},
transactionHash: '0x2',
type: TokenTransactionTypeV2.EarnDeposit,
type: TokenTransactionTypeV2.Deposit,
feeCurrencyId: undefined,
providerId: mockEarnPositions[0].appId,
appName: mockEarnPositions[0].appName,
}

const expectedSwapDepositStandbyTx = {
context: {
id: 'id-earn/saga-Earn/SwapDeposit',
tag: 'earn/saga',
description: 'Earn/SwapDeposit',
},
networkId: NetworkId['arbitrum-sepolia'],
...expectedDepositStandbyTx,
swap: {
inAmount: {
value: '100',
Expand All @@ -209,20 +204,6 @@ describe('depositSubmitSaga', () => {
tokenId: mockArbArbTokenId,
},
},
deposit: {
inAmount: {
value: '100',
tokenId: mockAaveArbUsdcTokenId,
},
outAmount: {
value: '100',
tokenId: mockArbUsdcTokenId,
},
providerId: mockEarnPositions[0].appId,
},
transactionHash: '0x2',
type: TokenTransactionTypeV2.EarnSwapDeposit,
feeCurrencyId: undefined,
}

const expectedApproveGasAnalyticsProperties = {
Expand Down Expand Up @@ -369,16 +350,18 @@ describe('depositSubmitSaga', () => {
fromTokenId: mockArbArbTokenId,
fromNetworkId: NetworkId['arbitrum-sepolia'],
fromAddress: mockArbArbAddress,
txType: TokenTransactionTypeV2.Deposit,
},
{
swapType: 'cross-chain',
fromTokenId: mockCusdTokenId,
fromNetworkId: NetworkId['celo-alfajores'],
fromAddress: mockCusdAddress,
txType: TokenTransactionTypeV2.CrossChainDeposit,
},
])(
'sends approve and swap-deposit ($swapType) transactions, navigates home and dispatches the success action (gas subsidy off)',
async ({ swapType, fromTokenId, fromNetworkId, fromAddress }) => {
async ({ swapType, fromTokenId, fromNetworkId, fromAddress, txType }) => {
jest.mocked(decodeFunctionData).mockReturnValue({
functionName: 'approve',
args: ['0xspenderAddress', BigInt(5e19)],
Expand Down Expand Up @@ -436,6 +419,7 @@ describe('depositSubmitSaga', () => {
tokenId: fromTokenId,
},
},
type: txType,
})
expect(AppAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_deposit_submit_start, {
...expectedAnalyticsProps,
Expand Down
38 changes: 12 additions & 26 deletions src/earn/saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,10 @@ export function* depositSubmitSaga(action: PayloadAction<DepositInfo>) {
return {
context: newTransactionContext(TAG, 'Earn/Deposit'),
networkId: fromNetworkId,
type: TokenTransactionTypeV2.EarnDeposit,
type:
mode === 'swap-deposit' && fromNetworkId !== poolNetworkId
? TokenTransactionTypeV2.CrossChainDeposit
: TokenTransactionTypeV2.Deposit,
inAmount: {
value: amount,
tokenId: pool.dataProps.withdrawTokenId,
Expand All @@ -194,35 +197,18 @@ export function* depositSubmitSaga(action: PayloadAction<DepositInfo>) {
value: amount,
tokenId: depositTokenId,
},
providerId: pool.appId,
appName: pool.appName,
transactionHash,
feeCurrencyId,
...(mode === 'swap-deposit' && {
swap: {
inAmount: { value: amount, tokenId: depositTokenId },
outAmount: { value: fromTokenAmount, tokenId: fromTokenId },
},
}),
}
}
const createSwapDepositStandbyTx = (
transactionHash: string,
feeCurrencyId?: string
): BaseStandbyTransaction => {
return {
context: newTransactionContext(TAG, 'Earn/SwapDeposit'),
networkId: fromNetworkId,
type: TokenTransactionTypeV2.EarnSwapDeposit,
swap: {
inAmount: { value: amount, tokenId: depositTokenId },
outAmount: { value: fromTokenAmount, tokenId: fromTokenId },
},
deposit: {
inAmount: { value: amount, tokenId: pool.dataProps.withdrawTokenId },
outAmount: { value: amount, tokenId: depositTokenId },
providerId: pool.appId,
},
transactionHash,
feeCurrencyId,
}
}
createDepositStandbyTxHandlers.push(
mode === 'deposit' ? createDepositStandbyTx : createSwapDepositStandbyTx
)
createDepositStandbyTxHandlers.push(createDepositStandbyTx)
} else {
Logger.info(TAG, 'More than 2 deposit transactions, using empty standby handlers')
createDepositStandbyTxHandlers.push(...preparedTransactions.map(() => () => null))
Expand Down
41 changes: 39 additions & 2 deletions src/transactions/feed/DepositOrWithdrawFeedItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { HomeEvents } from 'src/analytics/Events'
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'
import { mockCeloTokenId, mockCusdTokenId, mockUSDCTokenId } from 'test/values'

describe('DepositOrWithdrawFeedItem', () => {
const store = createMockStore()
Expand All @@ -20,7 +20,7 @@ describe('DepositOrWithdrawFeedItem', () => {
fees: [],
appName: 'Some Dapp',
inAmount: {
value: '100',
value: '50',
tokenId: mockCeloTokenId,
},
outAmount: {
Expand All @@ -35,6 +35,21 @@ describe('DepositOrWithdrawFeedItem', () => {
type: TokenTransactionTypeV2.Withdraw as const,
}

const crossChainDepositTransaction = {
...depositTransaction,
type: TokenTransactionTypeV2.CrossChainDeposit as const,
swap: {
inAmount: {
value: '100',
tokenId: mockCusdTokenId,
},
outAmount: {
value: '100',
tokenId: mockUSDCTokenId,
},
},
}

it('renders deposit correctly', () => {
const { getByText, getByTestId } = render(
<Provider store={store}>
Expand All @@ -46,6 +61,9 @@ describe('DepositOrWithdrawFeedItem', () => {
expect(getByText('transactionFeed.depositSubtitle, {"txAppName":"Some Dapp"}')).toBeTruthy()
expect(getByTestId('DepositOrWithdrawFeedItem/DEPOSIT-amount-crypto')).toBeTruthy()
expect(getByTestId('DepositOrWithdrawFeedItem/DEPOSIT-amount-local')).toBeTruthy()
expect(getByTestId('DepositOrWithdrawFeedItem/DEPOSIT-amount-crypto')).toHaveTextContent(
'-100.00 cUSD'
)
})

it('renders withdraw correctly', () => {
Expand All @@ -59,6 +77,25 @@ describe('DepositOrWithdrawFeedItem', () => {
expect(getByText('transactionFeed.withdrawSubtitle, {"txAppName":"Some Dapp"}')).toBeTruthy()
expect(getByTestId('DepositOrWithdrawFeedItem/WITHDRAW-amount-crypto')).toBeTruthy()
expect(getByTestId('DepositOrWithdrawFeedItem/WITHDRAW-amount-local')).toBeTruthy()
expect(getByTestId('DepositOrWithdrawFeedItem/WITHDRAW-amount-crypto')).toHaveTextContent(
'+50.00 CELO'
)
})

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

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

it('displays app name when available', () => {
Expand Down
3 changes: 3 additions & 0 deletions src/transactions/feed/DepositOrWithdrawFeedItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function Description({ transaction }: DescriptionProps) {

switch (transaction.type) {
case TokenTransactionTypeV2.Deposit:
case TokenTransactionTypeV2.CrossChainDeposit:
title = t('transactionFeed.depositTitle')
subtitle = t('transactionFeed.depositSubtitle', {
context: !txAppName ? 'noTxAppName' : undefined,
Expand Down Expand Up @@ -66,6 +67,7 @@ function AmountDisplay({ transaction, isLocal }: AmountDisplayProps) {

switch (transaction.type) {
case TokenTransactionTypeV2.Deposit:
case TokenTransactionTypeV2.CrossChainDeposit:
amountValue = new BigNumber(-transaction.outAmount.value)
localAmount = transaction.outAmount.localAmount
tokenId = transaction.outAmount.tokenId
Expand Down Expand Up @@ -132,6 +134,7 @@ export default function DepositOrWithdrawFeedItem({ transaction }: Props) {
status={transaction.status}
transactionType={transaction.type}
networkId={transaction.networkId}
hideNetworkIcon={transaction.type === TokenTransactionTypeV2.CrossChainDeposit}
/>
<Description transaction={transaction} />
<Amount transaction={transaction} />
Expand Down
3 changes: 2 additions & 1 deletion src/transactions/feed/TransactionDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ function TransactionDetails({ transaction, title, subtitle, children, retryHandl
// been initiated. Therefore for failed cross chain swaps, we should show the
// transaction in the default network explorer.
const showCrossChainSwapExplorer =
transaction.type === TokenTransactionTypeV2.CrossChainSwapTransaction &&
(transaction.type === TokenTransactionTypeV2.CrossChainSwapTransaction ||
transaction.type === TokenTransactionTypeV2.CrossChainDeposit) &&
transaction.status !== TransactionStatus.Failed

const openBlockExplorerHandler =
Expand Down
79 changes: 77 additions & 2 deletions src/transactions/feed/TransactionDetailsScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -574,10 +574,11 @@ describe('TransactionDetailsScreen', () => {

function depositTransaction({
status = TransactionStatus.Complete,
type = TokenTransactionTypeV2.Deposit,
...rest
}: Partial<DepositOrWithdraw>): DepositOrWithdraw {
return {
type: TokenTransactionTypeV2.Deposit,
type,
networkId: NetworkId['celo-alfajores'],
timestamp: 1234567890,
block: '123456',
Expand Down Expand Up @@ -702,6 +703,42 @@ describe('TransactionDetailsScreen', () => {
expect(getByText('transactionDetailsActions.showCompletedTransactionDetails')).toBeTruthy()
expect(getByTestId('DepositOrWithdraw/Swap/From')).toBeTruthy()
expect(getByTestId('DepositOrWithdraw/Swap/To')).toBeTruthy()
expect(getByTestId('DepositOrWithdraw/Network')).toHaveTextContent('Celo Alfajores')
})

it('renders swap details for deposit with cross chain swap', () => {
const transactionWithSwap = {
...depositTransaction({
status: TransactionStatus.Complete,
type: TokenTransactionTypeV2.CrossChainDeposit,
}),
swap: {
inAmount: {
value: '50',
tokenId: mockCeloTokenId,
},
outAmount: {
value: '100',
tokenId: mockEthTokenId,
},
},
}

const { getByText, getByTestId } = renderScreen({
transaction: transactionWithSwap,
storeOverrides: {
tokens: {
tokenBalances: mockTokenBalances,
},
},
})

expect(getByText('transactionDetailsActions.showCompletedTransactionDetails')).toBeTruthy()
expect(getByTestId('DepositOrWithdraw/Swap/From')).toBeTruthy()
expect(getByTestId('DepositOrWithdraw/Swap/To')).toBeTruthy()
expect(getByTestId('DepositOrWithdraw/Network')).toHaveTextContent(
'swapTransactionDetailPage.networkValue, {"fromNetwork":"Ethereum Sepolia","toNetwork":"Celo Alfajores"}'
)
})

it('renders network information', () => {
Expand All @@ -715,7 +752,7 @@ describe('TransactionDetailsScreen', () => {
expect(getByText(NETWORK_NAMES[NetworkId['celo-alfajores']])).toBeTruthy()
})

it('renders fees correctly', () => {
it('renders fees correctly for deposit only', () => {
const transactionWithFees = {
...depositTransaction({ status: TransactionStatus.Complete }),
fees: [
Expand All @@ -735,6 +772,44 @@ describe('TransactionDetailsScreen', () => {

expect(getByTestId('TransactionDetails/FeeRowItem')).toHaveTextContent('0.10 CELO')
})

it('renders fees correctly for cross chain swap and deposit', () => {
const transactionWithFees = {
...depositTransaction({ status: TransactionStatus.Complete }),
fees: [
{
type: FeeType.SecurityFee,
amount: {
value: '0.1',
tokenId: mockCeloTokenId,
},
},
{
type: FeeType.AppFee,
amount: {
value: '0.1',
tokenId: mockCusdTokenId,
},
},
{
type: FeeType.CrossChainFee,
amount: {
value: '0.11',
tokenId: mockCeloTokenId,
},
},
],
}

const { getAllByTestId } = renderScreen({
transaction: transactionWithFees,
})

expect(getAllByTestId('TransactionDetails/FeeRowItem')).toHaveLength(3)
expect(getAllByTestId('TransactionDetails/FeeRowItem')[0]).toHaveTextContent('0.10 CELO')
expect(getAllByTestId('TransactionDetails/FeeRowItem')[1]).toHaveTextContent('0.10 cUSD')
expect(getAllByTestId('TransactionDetails/FeeRowItem')[2]).toHaveTextContent('0.11 CELO')
})
})

describe('Claim Reward', () => {
Expand Down
2 changes: 2 additions & 0 deletions src/transactions/feed/TransactionDetailsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ function useHeaderTitle(transaction: TokenTransaction) {
case TokenTransactionTypeV2.Approval:
return t('transactionFeed.approvalTransactionTitle')
case TokenTransactionTypeV2.Deposit:
case TokenTransactionTypeV2.CrossChainDeposit:
return t('transactionFeed.depositTitle')
case TokenTransactionTypeV2.Withdraw:
return t('transactionFeed.withdrawTitle')
Expand Down Expand Up @@ -118,6 +119,7 @@ function TransactionDetailsScreen({ route }: Props) {
break
case TokenTransactionTypeV2.Deposit:
case TokenTransactionTypeV2.Withdraw:
case TokenTransactionTypeV2.CrossChainDeposit:
content = <DepositOrWithdrawContent transaction={transaction} />
break
case TokenTransactionTypeV2.ClaimReward:
Expand Down
1 change: 1 addition & 0 deletions src/transactions/feed/TransactionFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ function TransactionFeed() {
case TokenTransactionTypeV2.Deposit:
case TokenTransactionTypeV2.Withdraw:
case TokenTransactionTypeV2.ClaimReward:
case TokenTransactionTypeV2.CrossChainDeposit:
// These are handled by the FeedV2 only
return null
case TokenTransactionTypeV2.EarnDeposit:
Expand Down
Loading

0 comments on commit 299072a

Please sign in to comment.