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): prepare transactions and use on confirmation screen #6192

Draft
wants to merge 8 commits into
base: tomm/act-1389-0
Choose a base branch
from
62 changes: 47 additions & 15 deletions src/earn/EarnConfirmationScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import BigNumber from 'bignumber.js'
import React, { useMemo } from 'react'
import React, { useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { SafeAreaView, ScrollView, StyleSheet, Text, View } from 'react-native'
import SkeletonPlaceholder from 'react-native-skeleton-placeholder'
Expand All @@ -10,7 +10,7 @@ import Button, { BtnSizes } from 'src/components/Button'
import InLineNotification, { NotificationVariant } from 'src/components/InLineNotification'
import TokenDisplay from 'src/components/TokenDisplay'
import TokenIcon, { IconSize } from 'src/components/TokenIcon'
import { usePrepareWithdrawAndClaimTransactions } from 'src/earn/hooks'
import { usePrepareTransactions } from 'src/earn/hooks'
import { withdrawStatusSelector } from 'src/earn/selectors'
import { withdrawStart } from 'src/earn/slice'
import { EarnActiveAction } from 'src/earn/types'
Expand All @@ -29,13 +29,17 @@ import { Spacing } from 'src/styles/styles'
import { useTokenInfo } from 'src/tokens/hooks'
import { feeCurrenciesSelector } from 'src/tokens/selectors'
import { TokenBalance } from 'src/tokens/slice'
import Logger from 'src/utils/Logger'
import { getFeeCurrencyAndAmounts } from 'src/viem/prepareTransactions'
import { getSerializablePreparedTransactions } from 'src/viem/preparedTransactionSerialization'
import { walletAddressSelector } from 'src/web3/selectors'
import { isAddress } from 'viem'

type Props = NativeStackScreenProps<StackParamList, Screens.EarnConfirmationScreen>

const TAG = 'EarnConfirmationScreen'
const FETCH_UPDATED_TRANSACTIONS_DEBOUNCE_TIME = 250

export default function EarnConfirmationScreen({ route }: Props) {
const { t } = useTranslation()
const dispatch = useDispatch()
Expand Down Expand Up @@ -74,20 +78,48 @@ export default function EarnConfirmationScreen({ route }: Props) {
[withdrawToken, pool.pricePerShare, inputAmount]
)

// Will need this to handle preparing a claim transaction, a withdrawal transaction and a withdrawal and claim transaction
const {
result: prepareTransactionsResult,
loading: isPreparingTransactions,
error: prepareTransactionError,
} = usePrepareWithdrawAndClaimTransactions({
amount: new BigNumber(withdrawAmountInDepositToken).dividedBy(pool.pricePerShare[0]).toString(),
pool,
walletAddress,
feeCurrencies,
hooksApiUrl,
rewardsPositions,
useMax,
})
prepareTransactionsResult: { prepareTransactionsResult } = {},
refreshPreparedTransactions,
clearPreparedTransactions,
prepareTransactionError,
isPreparingTransactions,
} = usePrepareTransactions(mode, { rewardsPositions })

const handleRefreshPreparedTransactions = (
amount: BigNumber,
token: TokenBalance,
feeCurrencies: TokenBalance[]
) => {
if (!walletAddress || !isAddress(walletAddress)) {
Logger.error(TAG, 'Wallet address not set. Cannot refresh prepared transactions.')
return
}

return refreshPreparedTransactions({
amount: new BigNumber(amount).dividedBy(pool.pricePerShare[0]).toString(),
token,
walletAddress,
feeCurrencies,
pool,
hooksApiUrl,
shortcutId: mode,
useMax,
})
}

useEffect(() => {
clearPreparedTransactions()

const debouncedRefreshTransactions = setTimeout(() => {
return handleRefreshPreparedTransactions(
new BigNumber(withdrawAmountInDepositToken),
withdrawToken,
feeCurrencies
)
}, FETCH_UPDATED_TRANSACTIONS_DEBOUNCE_TIME)
return () => clearTimeout(debouncedRefreshTransactions)
}, [withdrawAmountInDepositToken, depositToken, feeCurrencies])

const onPress = () => {
if (prepareTransactionsResult?.type !== 'possible') {
Expand Down
16 changes: 11 additions & 5 deletions src/earn/EarnEnterAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import TokenIcon, { IconSize } from 'src/components/TokenIcon'
import Touchable from 'src/components/Touchable'
import CustomHeader from 'src/components/header/CustomHeader'
import EarnDepositBottomSheet from 'src/earn/EarnDepositBottomSheet'
import { usePrepareTransactions } from 'src/earn/prepareTransactions'
import { usePrepareTransactions } from 'src/earn/hooks'
import { getSwapToAmountInDecimals } from 'src/earn/utils'
import { CICOFlow } from 'src/fiatExchanges/utils'
import ArrowRightThick from 'src/icons/ArrowRightThick'
Expand Down Expand Up @@ -144,6 +144,7 @@ function EarnEnterAmount({ route }: Props) {
}

const {
// @ts-expect-error swapTransaction only exists on swap-deposit mode
prepareTransactionsResult: { prepareTransactionsResult, swapTransaction } = {},
refreshPreparedTransactions,
clearPreparedTransactions,
Expand All @@ -170,8 +171,7 @@ function EarnEnterAmount({ route }: Props) {
feeCurrencies,
pool,
hooksApiUrl,
// Mode === shortcutId, except for exit which is withdraw
shortcutId: mode === 'exit' ? 'withdraw' : mode,
shortcutId: mode,
useMax: maxPressed,
})
}
Expand Down Expand Up @@ -483,11 +483,17 @@ function EarnEnterAmount({ route }: Props) {
})}
description={t('earnFlow.enterAmount.notEnoughBalanceForGasWarning.description', {
feeTokenSymbol: prepareTransactionsResult.feeCurrencies[0].symbol,
network: NETWORK_NAMES[prepareTransactionsResult.feeCurrencies[0].networkId],
network:
NETWORK_NAMES[
prepareTransactionsResult.feeCurrencies[0].networkId as keyof typeof NETWORK_NAMES
],
})}
ctaLabel={t('earnFlow.enterAmount.notEnoughBalanceForGasWarning.noGasCta', {
feeTokenSymbol: feeCurrencies[0].symbol,
network: NETWORK_NAMES[prepareTransactionsResult.feeCurrencies[0].networkId],
network:
NETWORK_NAMES[
prepareTransactionsResult.feeCurrencies[0].networkId as keyof typeof NETWORK_NAMES
],
})}
onPressCta={() => {
AppAnalytics.track(EarnEvents.earn_deposit_add_gas_press, {
Expand Down
71 changes: 58 additions & 13 deletions src/earn/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { useMemo } from 'react'
import { useAsync } from 'react-async-hook'
import { prepareWithdrawAndClaimTransactions } from 'src/earn/prepareTransactions'
import { PrepareWithdrawAndClaimParams } from 'src/earn/types'
import { useAsync, useAsyncCallback } from 'react-async-hook'
import {
prepareClaimTransactions,
prepareDepositTransactions,
prepareWithdrawAndClaimTransactions,
prepareWithdrawTransactions,
} from 'src/earn/prepareTransactions'
import { EarnActiveAction } from 'src/earn/types'
import { fetchExchanges } from 'src/fiatExchanges/utils'
import { isAppSwapsEnabledSelector } from 'src/navigator/selectors'
import { userLocationDataSelector } from 'src/networkInfo/selectors'
import { earnPositionsSelector } from 'src/positions/selectors'
import { EarnPosition } from 'src/positions/types'
import { EarnPosition, Position } from 'src/positions/types'
import { useSelector } from 'src/redux/hooks'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
Expand All @@ -17,15 +22,6 @@ import { ensureError } from 'src/utils/ensureError'

const TAG = 'earn/hooks'

export function usePrepareWithdrawAndClaimTransactions(params: PrepareWithdrawAndClaimParams) {
return useAsync(() => prepareWithdrawAndClaimTransactions(params), [], {
onError: (err) => {
const error = ensureError(err)
Logger.error(TAG, 'usePrepareWithdrawAndClaimTransactions', error)
},
})
}

export function useEarnPositionProviderName(providerId: string) {
const pools = useSelector(earnPositionsSelector)
const providerName = pools.find((pool) => pool.appId === providerId)?.appName
Expand Down Expand Up @@ -94,3 +90,52 @@ export function useDepositEntrypointInfo({
}, [asyncExchanges.result])
return { hasDepositToken, hasTokensOnSameNetwork, hasTokensOnOtherNetworks, canCashIn, exchanges }
}

export function usePrepareTransactions(
mode: EarnActiveAction,
additionalArgs?: { rewardsPositions: Position[] }
) {
const getTransactionFunction = () => {
switch (mode) {
case 'deposit':
case 'swap-deposit':
return prepareDepositTransactions
case 'withdraw':
return prepareWithdrawTransactions
case 'claim-rewards':
return prepareClaimTransactions
case 'exit':
return prepareWithdrawAndClaimTransactions
default:
throw new Error(`Invalid mode: ${mode}`)
}
}

const prepareTransactions = useAsyncCallback(
async (args) => {
const modifiedArgs = { ...args }
// Add rewardsPositions passed to usePrepareTransactions if the mode is 'exit' or 'claim-rewards'
if (mode === 'exit' || mode === 'claim-rewards') {
modifiedArgs.rewardsPositions = additionalArgs?.rewardsPositions
}
// Get the appropriate function based on the mode
const prepareFunction = getTransactionFunction()
// Ensure the function is called with the necessary arguments
return prepareFunction(modifiedArgs)
},
{
onError: (err) => {
const error = ensureError(err)
Logger.error(TAG, 'usePrepareTransactions - Error:', error)
},
}
)

return {
prepareTransactionsResult: prepareTransactions.result,
refreshPreparedTransactions: prepareTransactions.execute,
clearPreparedTransactions: prepareTransactions.reset,
prepareTransactionError: prepareTransactions.error,
isPreparingTransactions: prepareTransactions.loading,
}
}
Loading
Loading