Skip to content

Commit

Permalink
Merge branch 'main' into l10n/main
Browse files Browse the repository at this point in the history
  • Loading branch information
valora-bot authored Jan 10, 2025
2 parents 7a0dc76 + ee71b25 commit 0125fc8
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 69 deletions.
5 changes: 2 additions & 3 deletions src/analytics/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ export enum QrScreenEvents {
}

export enum FeeEvents {
estimate_fee_failed = 'estimate_fee_failed',
estimate_fee_success = 'estimate_fee_success',
gas_fee_warning_impression = 'gas_fee_warning_impression',
gas_fee_warning_cta_press = 'gas_fee_warning_cta_press',
}

export enum TransactionEvents {
Expand Down Expand Up @@ -661,7 +661,6 @@ export enum EarnEvents {
earn_deposit_submit_error = 'earn_deposit_submit_error',
earn_deposit_submit_cancel = 'earn_deposit_submit_cancel',
earn_enter_amount_continue_press = 'earn_enter_amount_continue_press',
earn_deposit_add_gas_press = 'earn_deposit_add_gas_press',
earn_feed_item_select = 'earn_feed_item_select',
earn_collect_earnings_press = 'earn_collect_earnings_press',
earn_withdraw_submit_start = 'earn_withdraw_submit_start',
Expand Down
20 changes: 11 additions & 9 deletions src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
} from 'src/analytics/types'
import { ErrorMessages } from 'src/app/ErrorMessages'
import { AddAssetsActionType } from 'src/components/AddAssetsBottomSheet'
import { GasFeeWarningFlow } from 'src/components/GasFeeWarning'
import { TokenPickerOrigin } from 'src/components/TokenBottomSheet'
import { DappSection } from 'src/dapps/types'
import { BeforeDepositActionName, EarnActiveMode, SerializableRewardsInfo } from 'src/earn/types'
Expand Down Expand Up @@ -629,15 +630,17 @@ interface SendEventsProperties {
}

interface FeeEventsProperties {
[FeeEvents.estimate_fee_failed]: {
feeType: string
tokenAddress: string
error: string
[FeeEvents.gas_fee_warning_impression]: {
flow: GasFeeWarningFlow
errorType: 'need-decrease-spend-amount-for-gas' | 'not-enough-balance-for-gas'
tokenId: string
networkId: NetworkId
}
[FeeEvents.estimate_fee_success]: {
feeType: string
tokenAddress: string
usdFee: string
[FeeEvents.gas_fee_warning_cta_press]: {
flow: GasFeeWarningFlow
errorType: 'need-decrease-spend-amount-for-gas' | 'not-enough-balance-for-gas'
tokenId: string
networkId: NetworkId
}
}

Expand Down Expand Up @@ -1629,7 +1632,6 @@ interface EarnEventsProperties {
depositTokenAmount?: string
swapType?: SwapType // only for swap-deposit
} & EarnCommonProperties
[EarnEvents.earn_deposit_add_gas_press]: EarnCommonProperties & { gasTokenId: string }
[EarnEvents.earn_feed_item_select]: {
origin:
| TokenTransactionTypeV2.EarnDeposit
Expand Down
8 changes: 5 additions & 3 deletions src/analytics/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
[QrScreenEvents.qr_screen_copy_address]: ``,
[QrScreenEvents.qr_scanner_open]: `When unique "QR scanner" button is pressed`,
[QrScreenEvents.qr_scanned]: `When a QR code has been successfully scanned`,
[FeeEvents.estimate_fee_failed]: ``,
[FeeEvents.estimate_fee_success]: ``,
[FeeEvents.gas_fee_warning_impression]: `When the gas fee warning is shown to the user`,
[FeeEvents.gas_fee_warning_cta_press]: `When the user presses the CTA on the gas fee warning`,
[TransactionEvents.transaction_start]: `when a transaction is about to be submitted to the blockchain`,
[TransactionEvents.transaction_gas_estimated]: `when gas is estimated for a transaction or an already estimated gas is used in a transaction about to be submitted (only for contract-kit)`,
[TransactionEvents.transaction_hash_received]: `when a hash is received for a transaction`,
Expand Down Expand Up @@ -589,7 +589,6 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
[EarnEvents.earn_deposit_submit_error]: `When the deposit transaction fails`,
[EarnEvents.earn_deposit_submit_cancel]: `When the user cancels the deposit after submitting by cancelling PIN input`,
[EarnEvents.earn_enter_amount_continue_press]: `When a user taps continue on the earn enter amount`,
[EarnEvents.earn_deposit_add_gas_press]: `When the user doesn't have enough for gas when trying to deposit and clicks on the button to add gas token`,
[EarnEvents.earn_feed_item_select]: `When the users taps on an earn transaction feed item`,
[EarnEvents.earn_collect_earnings_press]: `When the user taps on the collect earnings button in the collect screen`,
[EarnEvents.earn_withdraw_submit_start]: `When the wallet is about to submit the withdraw and/or claim transactions to the network`,
Expand Down Expand Up @@ -659,7 +658,10 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
// [EarnEvents.earn_exit_pool_press]: `When the user taps on the exit pool button from the earn card in discover tab`,
// [EarnEvents.earn_deposit_more_press]: `When the user taps deposit more button from the earn card in discover tab`,
// [EarnEvents.earn_active_pools_card_press]: `When the user taps on the active pool card in discover tab.`,
// [EarnEvents.earn_deposit_add_gas_press]: `When the user doesn't have enough for gas when trying to deposit and clicks on the button to add gas token`,
// [AppEvents.multichain_beta_opt_in]: `When the user taps the Try it Now button on the multichain beta screen`,
// [AppEvents.multichain_beta_opt_out]: `When the user taps the No Thanks button on the multichain beta screen`,
// [AppEvents.multichain_beta_contact_support]: `When the user taps the Contact Support button on the multichain beta screen`,
// [FeeEvents.estimate_fee_failed]: ``,
// [FeeEvents.estimate_fee_success]: ``,
}
126 changes: 126 additions & 0 deletions src/components/GasFeeWarning.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { fireEvent, render } from '@testing-library/react-native'
import BigNumber from 'bignumber.js'
import React from 'react'
import { Provider } from 'react-redux'
import AppAnalytics from 'src/analytics/AppAnalytics'
import { FeeEvents } from 'src/analytics/Events'
import GasFeeWarning from 'src/components/GasFeeWarning'
import { NetworkId } from 'src/transactions/types'
import {
PreparedTransactionsNeedDecreaseSpendAmountForGas,
PreparedTransactionsNotEnoughBalanceForGas,
PreparedTransactionsPossible,
} from 'src/viem/prepareTransactions'
import { createMockStore } from 'test/utils'
import { mockArbEthTokenId, mockCeloTokenId, mockTokenBalances } from 'test/values'

const mockPreparedTransactionPossible: PreparedTransactionsPossible = {
type: 'possible' as const,
transactions: [],
feeCurrency: {
...mockTokenBalances[mockArbEthTokenId],
isNative: true,
balance: new BigNumber(10),
priceUsd: new BigNumber(1),
lastKnownPriceUsd: new BigNumber(1),
},
}

const mockPreparedTransactionNotEnoughCelo: PreparedTransactionsNotEnoughBalanceForGas = {
type: 'not-enough-balance-for-gas' as const,
feeCurrencies: [
{
...mockTokenBalances[mockCeloTokenId],
isNative: true,
balance: new BigNumber(0),
priceUsd: new BigNumber(1500),
lastKnownPriceUsd: new BigNumber(1500),
},
],
}

const mockPreparedTransactionNeedDecreaseEth: PreparedTransactionsNeedDecreaseSpendAmountForGas = {
type: 'need-decrease-spend-amount-for-gas' as const,
feeCurrency: {
...mockTokenBalances[mockArbEthTokenId],
isNative: true,
balance: new BigNumber(0),
priceUsd: new BigNumber(1500),
lastKnownPriceUsd: new BigNumber(1500),
},
maxGasFeeInDecimal: new BigNumber(1),
estimatedGasFeeInDecimal: new BigNumber(1),
decreasedSpendAmount: new BigNumber(1),
}

describe('GasFeeWarning', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('should return null if prepareTransactionsResult is undefined', () => {
const store = createMockStore()
const { queryByTestId } = render(
<Provider store={store}>
<GasFeeWarning flow={'Send'} />
</Provider>
)
expect(queryByTestId('GasFeeWarning')).toBeFalsy()
})
it('should return null if prepareTransactionsResult.type is possible', () => {
const store = createMockStore()
const { queryByTestId } = render(
<Provider store={store}>
<GasFeeWarning flow={'Send'} prepareTransactionsResult={mockPreparedTransactionPossible} />
</Provider>
)
expect(queryByTestId('GasFeeWarning')).toBeFalsy()
})
it.each`
scenario | flow | prepareTransactionsResult | feeCurrencyTokenId | title | description | ctaLabel
${'sending max amount of ETH'} | ${'Send'} | ${mockPreparedTransactionNeedDecreaseEth} | ${mockArbEthTokenId} | ${'gasFeeWarning.title, {"context":"Send","tokenSymbol":"ETH"}'} | ${'gasFeeWarning.descriptionMaxAmount, {"context":"Send","tokenSymbol":"ETH"}'} | ${'gasFeeWarning.ctaAction, {"context":"Send"}'}
${'sending with insufficient CELO'} | ${'Send'} | ${mockPreparedTransactionNotEnoughCelo} | ${mockCeloTokenId} | ${'gasFeeWarning.title, {"context":"Send","tokenSymbol":"CELO"}'} | ${undefined} | ${'gasFeeWarning.ctaBuy, {"tokenSymbol":"CELO"}'}
${'swapping max amount of ETH'} | ${'Swap'} | ${mockPreparedTransactionNeedDecreaseEth} | ${mockArbEthTokenId} | ${'gasFeeWarning.title, {"context":"Swap","tokenSymbol":"ETH"}'} | ${'gasFeeWarning.descriptionMaxAmount, {"context":"Swap","tokenSymbol":"ETH"}'} | ${'gasFeeWarning.ctaAction, {"context":"Swap"}'}
${'swapping with insufficient CELO'} | ${'Swap'} | ${mockPreparedTransactionNotEnoughCelo} | ${mockCeloTokenId} | ${'gasFeeWarning.title, {"context":"Swap","tokenSymbol":"CELO"}'} | ${undefined} | ${'gasFeeWarning.ctaBuy, {"tokenSymbol":"CELO"}'}
${'withdrawing max amount of ETH'} | ${'Withdraw'} | ${mockPreparedTransactionNeedDecreaseEth} | ${mockArbEthTokenId} | ${'gasFeeWarning.title, {"context":"Withdraw","tokenSymbol":"ETH"}'} | ${'gasFeeWarning.descriptionMaxAmount, {"context":"Withdraw","tokenSymbol":"ETH"}'} | ${'gasFeeWarning.ctaAction, {"context":"Withdraw"}'}
${'withdrawing with insufficient CELO'} | ${'Withdraw'} | ${mockPreparedTransactionNotEnoughCelo} | ${mockCeloTokenId} | ${'gasFeeWarning.title, {"context":"Withdraw","tokenSymbol":"CELO"}'} | ${undefined} | ${'gasFeeWarning.ctaBuy, {"tokenSymbol":"CELO"}'}
${'depositing max amount of ETH'} | ${'Deposit'} | ${mockPreparedTransactionNeedDecreaseEth} | ${mockArbEthTokenId} | ${'gasFeeWarning.title, {"context":"Deposit","tokenSymbol":"ETH"}'} | ${'gasFeeWarning.descriptionMaxAmount, {"context":"Deposit","tokenSymbol":"ETH"}'} | ${'gasFeeWarning.ctaAction, {"context":"Deposit"}'}
${'depositing with insufficient CELO'} | ${'Deposit'} | ${mockPreparedTransactionNotEnoughCelo} | ${mockCeloTokenId} | ${'gasFeeWarning.title, {"context":"Deposit","tokenSymbol":"CELO"}'} | ${undefined} | ${'gasFeeWarning.ctaBuy, {"tokenSymbol":"CELO"}'}
${'dapp transaction with max amount of ETH'} | ${'Dapp'} | ${mockPreparedTransactionNeedDecreaseEth} | ${mockArbEthTokenId} | ${'gasFeeWarning.title, {"context":"Dapp","tokenSymbol":"ETH"}'} | ${'gasFeeWarning.descriptionDapp, {"tokenSymbol":"ETH"}'} | ${undefined}
${'dapp transaction with insufficient CELO'} | ${'Dapp'} | ${mockPreparedTransactionNotEnoughCelo} | ${mockCeloTokenId} | ${'gasFeeWarning.title, {"context":"Dapp","tokenSymbol":"CELO"}'} | ${'gasFeeWarning.descriptionDapp, {"tokenSymbol":"CELO"}'} | ${undefined}
`(
'renders error correctly when $scenario',
({ flow, prepareTransactionsResult, feeCurrencyTokenId, title, description, ctaLabel }) => {
const store = createMockStore()
const onPressSmallerAmount = jest.fn()
const { getByTestId, getByText } = render(
<Provider store={store}>
<GasFeeWarning
flow={flow}
prepareTransactionsResult={prepareTransactionsResult}
onPressSmallerAmount={onPressSmallerAmount}
/>
</Provider>
)
expect(getByTestId('GasFeeWarning')).toBeTruthy()
expect(AppAnalytics.track).toHaveBeenCalledTimes(1)
expect(AppAnalytics.track).toHaveBeenCalledWith(FeeEvents.gas_fee_warning_impression, {
flow,
errorType: prepareTransactionsResult.type,
tokenId: feeCurrencyTokenId,
networkId:
feeCurrencyTokenId === mockArbEthTokenId
? NetworkId['arbitrum-sepolia']
: NetworkId['celo-alfajores'],
})
expect(getByText(title)).toBeTruthy()
expect(description ? getByText(description) : true).toBeTruthy()
expect(ctaLabel ? getByText(ctaLabel) : true).toBeTruthy()
if (ctaLabel) {
fireEvent.press(getByText(ctaLabel))
}
expect(onPressSmallerAmount).toHaveBeenCalledTimes(
ctaLabel && ctaLabel.includes('ctaAction') ? 1 : 0
)
}
)
})
124 changes: 124 additions & 0 deletions src/components/GasFeeWarning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'
import AppAnalytics from 'src/analytics/AppAnalytics'
import { FeeEvents } from 'src/analytics/Events'
import InLineNotification, { NotificationVariant } from 'src/components/InLineNotification'
import { CICOFlow } from 'src/fiatExchanges/types'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { Spacing } from 'src/styles/styles'
import { PreparedTransactionsResult } from 'src/viem/prepareTransactions'

export type GasFeeWarningFlow = 'Send' | 'Swap' | 'Withdraw' | 'Deposit' | 'Dapp'

function GasFeeWarning({
prepareTransactionsResult,
flow,
onPressSmallerAmount,
}: {
prepareTransactionsResult?: PreparedTransactionsResult
flow: GasFeeWarningFlow
onPressSmallerAmount?: (amount: string) => void
}) {
const { t } = useTranslation()

const feeCurrency = prepareTransactionsResult
? prepareTransactionsResult.type === 'not-enough-balance-for-gas'
? prepareTransactionsResult.feeCurrencies[0]
: prepareTransactionsResult.feeCurrency
: undefined

useEffect(() => {
if (feeCurrency && prepareTransactionsResult && prepareTransactionsResult.type !== 'possible') {
AppAnalytics.track(FeeEvents.gas_fee_warning_impression, {
flow,
errorType: prepareTransactionsResult.type,
tokenId: feeCurrency.tokenId,
networkId: feeCurrency.networkId,
})
}
}, [flow, prepareTransactionsResult, feeCurrency])

const { title, description, ctaLabel, onPressCta } = useMemo(() => {
if (
!feeCurrency ||
!prepareTransactionsResult ||
prepareTransactionsResult.type === 'possible'
) {
return {}
}
const title = t('gasFeeWarning.title', {
context: flow,
tokenSymbol: feeCurrency.symbol,
})
const trackCtaAnalytics = () => {
AppAnalytics.track(FeeEvents.gas_fee_warning_cta_press, {
flow,
tokenId: feeCurrency.tokenId,
errorType: prepareTransactionsResult.type,
networkId: feeCurrency.networkId,
})
}
if (flow === 'Dapp') {
return {
title,
description: t('gasFeeWarning.descriptionDapp', { tokenSymbol: feeCurrency.symbol }),
ctaLabel: undefined,
onPressCta: undefined,
}
} else if (prepareTransactionsResult.type === 'not-enough-balance-for-gas') {
return {
title,
ctaLabel: t('gasFeeWarning.ctaBuy', { tokenSymbol: feeCurrency.symbol }),
onPressCta: () => {
trackCtaAnalytics()
navigate(Screens.FiatExchangeAmount, {
tokenId: feeCurrency.tokenId,
flow: CICOFlow.CashIn,
tokenSymbol: feeCurrency.symbol,
})
},
}
} else {
return {
title,
description: t('gasFeeWarning.descriptionMaxAmount', {
context: flow,
tokenSymbol: feeCurrency.symbol,
}),
ctaLabel: t('gasFeeWarning.ctaAction', { context: flow }),
onPressCta: () => {
trackCtaAnalytics()
onPressSmallerAmount?.(prepareTransactionsResult.decreasedSpendAmount.toString())
},
}
}
}, [flow, prepareTransactionsResult, feeCurrency])

if (!title) {
return false
}

return (
<InLineNotification
variant={NotificationVariant.Warning}
title={title}
description={description ?? null}
ctaLabel={ctaLabel}
onPressCta={onPressCta}
style={styles.warning}
testID={'GasFeeWarning'}
/>
)
}

const styles = StyleSheet.create({
warning: {
marginTop: Spacing.Regular16,
paddingHorizontal: Spacing.Regular16,
borderRadius: 16,
},
})

export default GasFeeWarning
2 changes: 1 addition & 1 deletion src/components/InLineNotification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function InLineNotification({
)}
<View style={styles.contentContainer}>
{!!title && <Text style={styles.titleText}>{title}</Text>}
<Text style={[styles.bodyText]}>{description}</Text>
{!!description && <Text style={[styles.bodyText]}>{description}</Text>}
</View>
</View>

Expand Down
Loading

0 comments on commit 0125fc8

Please sign in to comment.