Skip to content

CP-9946 & CP-9947: Add Action Buttons to Watchlist #2336

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

Merged
merged 11 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/core-mobile/app/consts/coingecko.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const AVAX_COINGECKO_ID = 'avalanche-2'

export const BITCOIN_COINGECKO_ID = 'bitcoin'

export const ETHEREUM_COINGECKO_ID = 'ethereum'
2 changes: 2 additions & 0 deletions packages/core-mobile/app/consts/swap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const AVAX_TOKEN_ID = 'AvalancheAVAX'
export const USDC_TOKEN_ID = '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E'
4 changes: 0 additions & 4 deletions packages/core-mobile/app/contexts/PosthogContext/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
selectIsBridgeBtcBlocked,
selectIsBridgeEthBlocked,
selectIsBrowserBlocked,
selectIsCoinbasePayBlocked,
selectIsEarnBlocked,
selectIsEventsBlocked,
selectIsSendNftBlockedAndroid,
Expand All @@ -39,7 +38,6 @@ export interface PosthogContextState {
sendNftBlockediOS: boolean
sendNftBlockedAndroid: boolean
sentrySampleRate: number
coinbasePayBlocked: boolean
}

export const PosthogContextProvider = ({
Expand All @@ -58,7 +56,6 @@ export const PosthogContextProvider = ({
const sendNftBlockedAndroid = useSelector(selectIsSendNftBlockedAndroid)
const eventsBlocked = useSelector(selectIsEventsBlocked)
const sentrySampleRate = useSelector(selectSentrySampleRate)
const coinbasePayBlocked = useSelector(selectIsCoinbasePayBlocked)
const browserBlocked = useSelector(selectIsBrowserBlocked)

const { timeoutPassed } = useAppBackgroundTracker({
Expand Down Expand Up @@ -122,7 +119,6 @@ export const PosthogContextProvider = ({
sendNftBlockediOS,
sendNftBlockedAndroid,
sentrySampleRate,
coinbasePayBlocked,
browserBlocked
}}>
{children}
Expand Down
43 changes: 43 additions & 0 deletions packages/core-mobile/app/hooks/earn/useHasEnoughAvaxToStake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useCChainBalance } from 'hooks/earn/useCChainBalance'
import useStakingParams from 'hooks/earn/useStakingParams'
import { useEffect, useState } from 'react'
import { useGetClaimableBalance } from 'hooks/earn/useGetClaimableBalance'
import { TokenUnit } from '@avalabs/core-utils-sdk'
import useCChainNetwork from 'hooks/earn/useCChainNetwork'
import { useGetStuckBalance } from 'hooks/earn/useGetStuckBalance'

export const useHasEnoughAvaxToStake = (): {
hasEnoughAvax: boolean | undefined
} => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice hook

const { minStakeAmount } = useStakingParams()
const cChainBalance = useCChainBalance()
const claimableBalance = useGetClaimableBalance()
const cChainNetwork = useCChainNetwork()
const stuckBalance = useGetStuckBalance()
const cChainNetworkToken = cChainNetwork?.networkToken
const [hasEnoughAvax, setHasEnoughAvax] = useState<boolean | undefined>(
undefined
)

useEffect(() => {
if (cChainBalance.data?.balance !== undefined && cChainNetworkToken) {
const availableAvax = new TokenUnit(
cChainBalance.data.balance,
cChainNetworkToken.decimals,
cChainNetworkToken.symbol
)
.add(claimableBalance ?? 0)
.add(stuckBalance ?? 0)

setHasEnoughAvax(availableAvax.gt(minStakeAmount))
}
}, [
cChainBalance?.data?.balance,
minStakeAmount,
stuckBalance,
claimableBalance,
cChainNetworkToken
])

return { hasEnoughAvax }
}
11 changes: 8 additions & 3 deletions packages/core-mobile/app/hooks/useCoinGeckoId.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { useTokenInfoContext } from '@avalabs/core-bridge-sdk'
import {
AVAX_COINGECKO_ID,
BITCOIN_COINGECKO_ID,
ETHEREUM_COINGECKO_ID
} from 'consts/coingecko'

const KNOWN_IDS: { [key: string]: string } = {
BTC: 'bitcoin',
AVAX: 'avalanche-2',
ETH: 'ethereum'
BTC: BITCOIN_COINGECKO_ID,
AVAX: AVAX_COINGECKO_ID,
ETH: ETHEREUM_COINGECKO_ID
}

export const useCoinGeckoId = (tokenSymbol?: string): string | undefined => {
Expand Down
6 changes: 3 additions & 3 deletions packages/core-mobile/app/hooks/useIsUIDisabled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ const enabledUIs: Partial<Record<UI, number[]>> = {
ChainId.ETHEREUM_TEST_RINKEBY,
ChainId.ETHEREUM_TEST_SEPOLIA
],
[UI.Swap]: [ChainId.AVALANCHE_MAINNET_ID],
[UI.Buy]: [ChainId.AVALANCHE_MAINNET_ID, ChainId.AVALANCHE_TESTNET_ID]
[UI.Swap]: [ChainId.AVALANCHE_MAINNET_ID]
}

// The list of features we want to disable on certain networks (blacklist)
Expand All @@ -40,7 +39,8 @@ const disabledUIs: Partial<Record<UI, number[]>> = {
ChainId.SWIMMER,
ChainId.SWIMMER_TESTNET
],
[UI.WalletConnect]: [] // empty array means this feature shouldn't be disabled on any network
[UI.WalletConnect]: [], // empty array means this feature shouldn't be disabled on any network
[UI.Buy]: []
}

export const useIsUIDisabled = (ui: UI): boolean => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { UseQueryResult, useQuery } from '@tanstack/react-query'
import { ReactQueryKeys } from 'consts/reactQueryKeys'
import { useExchangeRates } from 'hooks/defi/useExchangeRates'
import { useSelector } from 'react-redux'
import { TrendingToken } from 'services/token/types'
import WatchlistService from 'services/watchlist/WatchlistService'
import { selectSelectedCurrency } from 'store/settings/currency'

export const useGetTrendingTokens = <TData = TrendingToken[]>(
select?: (data: TrendingToken[]) => TData
): UseQueryResult<TData, Error> => {
const selectedCurrency = useSelector(selectSelectedCurrency)
const { data } = useExchangeRates()
const exchangeRate = data?.usd?.[selectedCurrency.toLowerCase()]

return useQuery({
queryKey: [ReactQueryKeys.WATCHLIST_TRENDING_TOKENS_AND_CHARTS],
queryFn: async () => WatchlistService.getTrendingTokens(),
queryKey: [
ReactQueryKeys.WATCHLIST_TRENDING_TOKENS_AND_CHARTS,
exchangeRate
],
queryFn: async () => WatchlistService.getTrendingTokens(exchangeRate),
refetchInterval: 120000, // 2 mins
select
})
Expand Down
3 changes: 2 additions & 1 deletion packages/core-mobile/app/navigation/AppNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ enum WalletScreens {
Earn = 'WalletScreens.Earn',
Notifications = 'WalletScreens.Notifications',
DeFiProtocolDetails = 'WalletScreens.DeFiProtocolDetails',
SendFeedback = 'WalletScreens.SendFeedback'
SendFeedback = 'WalletScreens.SendFeedback',
Halliday = 'WalletScreens.Halliday'
}

enum ReceiveTokensScreens {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import TestnetBanner from 'components/TestnetBanner'
import { selectIsDeveloperMode } from 'store/settings/advanced'
import { NFTMetadataProvider } from 'contexts/NFTItemsContext'
import { BridgeProvider } from 'contexts/BridgeContext'
import { HallidayWebView } from 'screens/bridge/components/HallidayWebView'
import { BridgeStackParamList } from '../wallet/BridgeScreenStack'
import {
AddEthereumChainV2Params,
Expand Down Expand Up @@ -119,6 +120,7 @@ export type WalletScreenStackParams = {
[AppNavigation.Wallet.Buy]:
| NavigatorScreenParams<BuyStackParamList>
| undefined
[AppNavigation.Wallet.Halliday]: undefined
[AppNavigation.Wallet.Bridge]:
| NavigatorScreenParams<BridgeStackParamList>
| undefined
Expand Down Expand Up @@ -305,6 +307,13 @@ function WalletScreenStack(props: Props): JSX.Element {
name={AppNavigation.Wallet.Swap}
component={SwapScreenStack}
/>
<WalletScreenS.Screen
options={{
...MainHeaderOptions({ title: 'Halliday' })
}}
name={AppNavigation.Wallet.Halliday}
component={HallidayWebView}
/>
<WalletScreenS.Screen
options={{
headerShown: false
Expand Down
10 changes: 9 additions & 1 deletion packages/core-mobile/app/navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { StakeSetupStackParamList } from './wallet/EarnScreenStack/StakeSetupScr
import { RecoveryMethodsStackParamList } from './onboarding/RecoveryMethodsStack'
import { SeedlessExportStackParamList } from './wallet/SeedlessExportStack'
import { SettingRecoveryMethodsStackParamList } from './wallet/SettingRecoveryMethodsStack'
import { BuyStackParamList } from './wallet/BuyScreenStack'

export type { RootScreenStackParamList }

Expand Down Expand Up @@ -109,7 +110,7 @@ export type UpdateContactV2Params = {
}

export type BuyCarefullyParams = {
tokenType: string
provider: string
}

export type ApprovalPopupParams = {
Expand Down Expand Up @@ -227,6 +228,13 @@ export type WalletScreenProps<T extends keyof WalletScreenStackParams> =
RootStackScreenProps<keyof RootScreenStackParamList>
>

/** ROOT -> WALLET -> BUY **/
export type BuyScreenProps<T extends keyof BuyStackParamList> =
CompositeScreenProps<
StackScreenProps<BuyStackParamList, T>,
WalletScreenProps<keyof WalletScreenStackParams>
>

/** ROOT -> WALLET -> DRAWER **/
export type DrawerScreenProps<T extends keyof DrawerParamList> =
CompositeScreenProps<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { useSelector } from 'react-redux'
import { selectIsBridgeBlocked } from 'store/posthog'
import AnalyticsService from 'services/analytics/AnalyticsService'
import Bridge from 'screens/bridge/Bridge'
import { HallidayWebView } from 'screens/bridge/components/HallidayWebView'

export type BridgeStackParamList = {
[AppNavigation.Bridge.Bridge]: { initialTokenSymbol: string } | undefined
Expand All @@ -27,7 +26,6 @@ export type BridgeStackParamList = {
bridgeTokenList: AssetBalance[] | undefined
}
[AppNavigation.Bridge.HideWarning]: undefined
[AppNavigation.Bridge.Halliday]: undefined
}

const BridgeStack = createStackNavigator<BridgeStackParamList>()
Expand Down Expand Up @@ -68,13 +66,6 @@ function BridgeScreenStack(): JSX.Element {
name={AppNavigation.Bridge.HideWarning}
component={HideTransactionWarningModal}
/>
<BridgeStack.Screen
options={{
...SubHeaderOptions('Halliday')
}}
name={AppNavigation.Bridge.Halliday}
component={HallidayWebView}
/>
</BridgeStack.Group>
</BridgeStack.Navigator>
{isBridgeBlocked && (
Expand Down
10 changes: 5 additions & 5 deletions packages/core-mobile/app/navigation/wallet/BuyScreenStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import AppNavigation from 'navigation/AppNavigation'
import Buy from 'screens/rpc/buy/Buy'

export type BuyStackParamList = {
[AppNavigation.Buy.Buy]: undefined
[AppNavigation.Buy.Buy]: {
showAvaxWarning?: boolean
}
}

const BuyStack = createStackNavigator<BuyStackParamList>()

const BuyScreenStack = () => {
const BuyScreenStack = (): JSX.Element => {
const { theme } = useApplicationContext()

return (
Expand All @@ -30,11 +32,9 @@ const BuyScreenStack = () => {
},
...TransitionPresets.SlideFromRightIOS
}}>
<BuyStack.Screen name={AppNavigation.Buy.Buy} component={BuyScreen} />
<BuyStack.Screen name={AppNavigation.Buy.Buy} component={Buy} />
</BuyStack.Navigator>
)
}

const BuyScreen = () => <Buy />

export default BuyScreenStack
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { SwapContextProvider } from 'contexts/SwapContext/SwapContext'
import { useNavigation } from '@react-navigation/native'
import FeatureBlocked from 'screens/posthog/FeatureBlocked'
import * as Navigation from 'utils/Navigation'
import { AVAX_TOKEN_ID, USDC_TOKEN_ID } from 'consts/swap'

export type SwapStackParamList = {
[AppNavigation.Swap.Swap]:
Expand Down Expand Up @@ -51,8 +52,8 @@ function SwapScreenStack(): JSX.Element {
name={AppNavigation.Swap.Swap}
component={SwapView}
initialParams={{
initialTokenIdFrom: 'AvalancheAVAX', //AVAX
initialTokenIdTo: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E' //USDC
initialTokenIdFrom: AVAX_TOKEN_ID,
initialTokenIdTo: USDC_TOKEN_ID
}}
/>
</SwapStack.Navigator>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import AppNavigation from 'navigation/AppNavigation'
import { BridgeScreenProps } from 'navigation/types'
import React from 'react'
import { useDispatch } from 'react-redux'
import AnalyticsService from 'services/analytics/AnalyticsService'
import { setViewOnce, ViewOnceKey } from 'store/viewOnce'

type NavigationProps = BridgeScreenProps<typeof AppNavigation.Bridge.Bridge>
Expand All @@ -16,7 +17,8 @@ export const HallidayBanner = (): React.JSX.Element => {
const { navigate } = useNavigation<NavigationProps['navigation']>()

const openHalliday = async (): Promise<void> => {
navigate(AppNavigation.Bridge.Halliday)
AnalyticsService.capture('HallidayBuyClicked')
navigate(AppNavigation.Wallet.Halliday)
}

const dismissHallidayBanner = (): void => {
Expand Down
46 changes: 12 additions & 34 deletions packages/core-mobile/app/screens/earn/SmartStakeAmount.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { useNavigation } from '@react-navigation/native'
import { useCChainBalance } from 'hooks/earn/useCChainBalance'
import useStakingParams from 'hooks/earn/useStakingParams'
import AppNavigation from 'navigation/AppNavigation'
import { StakeSetupScreenProps } from 'navigation/types'
import React, { useEffect, useState } from 'react'
import { StyleSheet, View } from 'react-native'
import Spinner from 'components/animation/Spinner'
import { useGetClaimableBalance } from 'hooks/earn/useGetClaimableBalance'
import { TokenUnit } from '@avalabs/core-utils-sdk'
import useCChainNetwork from 'hooks/earn/useCChainNetwork'
import { useGetStuckBalance } from 'hooks/earn/useGetStuckBalance'
import { useHasEnoughAvaxToStake } from 'hooks/earn/useHasEnoughAvaxToStake'
import NotEnoughAvax from './NotEnoughAvax'
import StakingAmount from './StakingAmount'

Expand All @@ -25,42 +20,25 @@ enum BalanceStates {

const SmartStakeAmount = (): React.JSX.Element => {
const { navigate } = useNavigation<ScreenProps['navigation']>()
const { minStakeAmount } = useStakingParams()
const cChainBalance = useCChainBalance()
const { hasEnoughAvax } = useHasEnoughAvaxToStake()
const [balanceState, setBalanceState] = useState(BalanceStates.UNKNOWN)
const claimableBalance = useGetClaimableBalance()
const cChainNetwork = useCChainNetwork()
const stuckBalance = useGetStuckBalance()
const cChainNetworkToken = cChainNetwork?.networkToken

useEffect(() => {
if (cChainBalance.data?.balance !== undefined && cChainNetworkToken) {
const availableAvax = new TokenUnit(
cChainBalance.data.balance,
cChainNetworkToken.decimals,
cChainNetworkToken.symbol
)
.add(claimableBalance ?? 0)
.add(stuckBalance ?? 0)
const notEnoughAvax = availableAvax.lt(minStakeAmount)
if (hasEnoughAvax === undefined) return

if (notEnoughAvax) {
setBalanceState(BalanceStates.INSUFFICIENT)
} else {
setBalanceState(BalanceStates.SUFFICIENT)
}
if (hasEnoughAvax) {
setBalanceState(BalanceStates.SUFFICIENT)
} else {
setBalanceState(BalanceStates.INSUFFICIENT)
}
}, [
cChainBalance?.data?.balance,
minStakeAmount,
stuckBalance,
claimableBalance,
cChainNetworkToken
])
}, [hasEnoughAvax])

const renderNotEnoughAvax = (): React.JSX.Element => {
const navToBuy = (): void => {
navigate(AppNavigation.Wallet.Buy)
navigate(AppNavigation.Wallet.Buy, {
screen: AppNavigation.Buy.Buy,
params: { showAvaxWarning: true }
})
}
const navToReceive = (): void => {
navigate(AppNavigation.Wallet.ReceiveTokens)
Expand Down
Loading
Loading