diff --git a/suite-native/accounts/package.json b/suite-native/accounts/package.json index 226d73ad6de0..4fe2085c9253 100644 --- a/suite-native/accounts/package.json +++ b/suite-native/accounts/package.json @@ -32,8 +32,9 @@ "@suite-native/intl": "workspace:*", "@suite-native/navigation": "workspace:*", "@suite-native/settings": "workspace:*", - "@suite-native/tokens": "workspace:*", "@suite-native/staking": "workspace:*", + "@suite-native/toasts": "workspace:*", + "@suite-native/tokens": "workspace:*", "@trezor/styles": "workspace:*", "jotai": "1.9.1", "proxy-memoize": "2.0.2", diff --git a/suite-native/accounts/src/components/AccountSectionTitle.tsx b/suite-native/accounts/src/components/AccountSectionTitle.tsx index ed531eac8a5c..3ef9a28837a1 100644 --- a/suite-native/accounts/src/components/AccountSectionTitle.tsx +++ b/suite-native/accounts/src/components/AccountSectionTitle.tsx @@ -1,13 +1,16 @@ import React, { useMemo } from 'react'; import { useSelector } from 'react-redux'; -import { HStack, VStack, Text } from '@suite-native/atoms'; -import { CryptoAmountFormatter, FiatAmountFormatter } from '@suite-native/formatters'; +import { selectCurrentFiatRates } from '@suite-common/wallet-core'; import { Account } from '@suite-common/wallet-types'; import { getAccountFiatBalance } from '@suite-common/wallet-utils'; -import { getCryptoBalanceWithStaking } from '@suite-native/staking'; +import { HStack, Text, VStack } from '@suite-native/atoms'; +import { CryptoAmountFormatter, FiatAmountFormatter } from '@suite-native/formatters'; import { selectFiatCurrencyCode } from '@suite-native/settings'; -import { selectCurrentFiatRates } from '@suite-common/wallet-core'; +import { + NativeStakingRootState, + selectAccountCryptoBalanceWithStaking, +} from '@suite-native/staking'; type AccountSectionTitleProps = { account: Account; @@ -21,6 +24,9 @@ export const AccountSectionTitle: React.FC = ({ }) => { const localCurrency = useSelector(selectFiatCurrencyCode); const rates = useSelector(selectCurrentFiatRates); + const cryptoBalanceWithStaking = useSelector((state: NativeStakingRootState) => + selectAccountCryptoBalanceWithStaking(state, account.key), + ); const fiatBalance = useMemo(() => { return getAccountFiatBalance({ account, localCurrency, rates }); @@ -38,7 +44,7 @@ export const AccountSectionTitle: React.FC = ({ value={fiatBalance} /> void; }; @@ -22,8 +24,14 @@ const contentContainerStyle = prepareNativeStyle(utils => ({ })); export const AccountSelectBottomSheet = React.memo( - ({ data, onSelectAccount, onClose }: AccountSelectBottomSheetProps) => { + ({ + data, + onSelectAccount, + isStakingPressable = false, + onClose, + }: AccountSelectBottomSheetProps) => { const { applyStyle } = useNativeStyles(); + const { showToast } = useToast(); const renderItem = useCallback( ({ item }: { item: AccountSelectBottomSheetSection }) => { @@ -36,12 +44,30 @@ export const AccountSelectBottomSheet = React.memo( {...item} hasBackground showDivider + isInModal={true} onPress={() => onSelectAccount(item)} /> ); case 'staking': return ( - {}} /> + { + if (isStakingPressable) { + onSelectAccount({ + account: item.account, + isStaking: true, + hasAnyKnownTokens: false, + }); + } else { + showToast({ + variant: 'warning', + message: 'Staking is not available in this context.', + }); + } + }} + /> ); case 'token': const { token, account } = item; @@ -63,7 +89,7 @@ export const AccountSelectBottomSheet = React.memo( return null; } }, - [onSelectAccount], + [isStakingPressable, onSelectAccount, showToast], ); return ( diff --git a/suite-native/accounts/src/components/AccountsList/AccountsList.tsx b/suite-native/accounts/src/components/AccountsList/AccountsList.tsx index f22089f3caaa..7f50e2142466 100644 --- a/suite-native/accounts/src/components/AccountsList/AccountsList.tsx +++ b/suite-native/accounts/src/components/AccountsList/AccountsList.tsx @@ -20,12 +20,14 @@ type AccountsListProps = { onSelectAccount: OnSelectAccount; filterValue?: string; hideTokensIntoModal?: boolean; + isStakingPressable?: boolean; }; export const AccountsList = ({ onSelectAccount, filterValue = '', hideTokensIntoModal = false, + isStakingPressable = false, }: AccountsListProps) => { const groupedAccounts = useSelector((state: NativeAccountsRootState) => selectFilteredDeviceAccountsGroupedByNetworkAccountType(state, filterValue), @@ -61,7 +63,6 @@ export const AccountsList = ({ ))} @@ -71,6 +72,7 @@ export const AccountsList = ({ ); diff --git a/suite-native/accounts/src/components/AccountsList/AccountsListItem.tsx b/suite-native/accounts/src/components/AccountsList/AccountsListItem.tsx index 6ae8920f345c..517ddf32bf70 100644 --- a/suite-native/accounts/src/components/AccountsList/AccountsListItem.tsx +++ b/suite-native/accounts/src/components/AccountsList/AccountsListItem.tsx @@ -17,19 +17,16 @@ import { selectNumberOfAccountTokensWithFiatRates, TokensRootState, } from '@suite-native/tokens'; -import { - NativeStakingRootState, - selectAccountHasStaking, - StakingBadge, -} from '@suite-native/staking'; +import { NativeStakingRootState, selectAccountHasStaking } from '@suite-native/staking'; import { NativeAccountsRootState, selectAccountFiatBalance } from '../../selectors'; import { OnSelectAccount } from '../../types'; import { AccountsListItemBase } from './AccountsListItemBase'; +import { StakingBadge } from './StakingBadge'; export type AccountListItemProps = { account: Account; - hideBadges?: boolean; + isInModal?: boolean; onPress?: OnSelectAccount; disabled?: boolean; @@ -58,7 +55,7 @@ export const AccountsListItem = ({ account, onPress, disabled, - hideBadges = false, + isInModal = false, hasBackground = false, isFirst = false, isLast = false, @@ -90,9 +87,9 @@ export const AccountsListItem = ({ }, [account, accountHasAnyTokens, onPress]); const doesCoinSupportTokens = isCoinWithTokens(account.symbol); - const shouldShowAccountLabel = !doesCoinSupportTokens || hideBadges; - const shouldShowTokenBadge = accountHasAnyTokens && hideBadges; - const shouldShowStakingBadge = accountHasStaking && hideBadges; + const shouldShowAccountLabel = !doesCoinSupportTokens || !isInModal; + const shouldShowTokenBadge = accountHasAnyTokens && !isInModal; + const shouldShowStakingBadge = accountHasStaking && !isInModal; return ( { return ( } - title="Staking" + icon={} + title={} mainValue={ { +export const StakingBadge = (props: Partial) => { return ( ; onSelectAccount: OnSelectAccount; + isStakingPressable?: boolean; }; export const TokenSelectBottomSheet = ({ bottomSheetAccountAtom, onSelectAccount, + isStakingPressable = false, }: TokenSelectBottomSheetProps) => { const [selectedAccount, setSelectedAccount] = useAtom(bottomSheetAccountAtom); @@ -44,6 +46,7 @@ export const TokenSelectBottomSheet = ({ onSelectAccount={handleSelectAccount} data={data} onClose={handleClose} + isStakingPressable={isStakingPressable} /> ); }; diff --git a/suite-native/accounts/src/index.ts b/suite-native/accounts/src/index.ts index be8a7acb8a02..0a64a51e3827 100644 --- a/suite-native/accounts/src/index.ts +++ b/suite-native/accounts/src/index.ts @@ -2,6 +2,7 @@ export * from './components/AddAccountsButton'; export * from './components/AccountsList/AccountsList'; export * from './components/AccountsList/AccountsListItem'; export * from './components/AccountsList/AccountsListItemBase'; +export * from './components/AccountsList/StakingBadge'; export * from './components/SearchableAccountsListScreenHeader'; export * from './components/SelectableNetworkItem'; export * from './components/AccountsList/AccountsListTokenItem'; diff --git a/suite-native/accounts/src/types.ts b/suite-native/accounts/src/types.ts index 3317894582a2..99ecc423b8bd 100644 --- a/suite-native/accounts/src/types.ts +++ b/suite-native/accounts/src/types.ts @@ -4,6 +4,7 @@ export type GroupedByTypeAccounts = Record; export type OnSelectAccount = (params: { account: Account; + isStaking?: boolean; tokenAddress?: TokenAddress; hasAnyKnownTokens: boolean; }) => void; diff --git a/suite-native/accounts/tsconfig.json b/suite-native/accounts/tsconfig.json index 8128c10e529a..d4b06c7edee5 100644 --- a/suite-native/accounts/tsconfig.json +++ b/suite-native/accounts/tsconfig.json @@ -35,6 +35,8 @@ { "path": "../intl" }, { "path": "../navigation" }, { "path": "../settings" }, + { "path": "../staking" }, + { "path": "../toasts" }, { "path": "../tokens" }, { "path": "../../packages/styles" } ] diff --git a/suite-native/app/package.json b/suite-native/app/package.json index 88427102b246..dd06171a07f8 100644 --- a/suite-native/app/package.json +++ b/suite-native/app/package.json @@ -65,6 +65,7 @@ "@suite-native/module-receive": "workspace:*", "@suite-native/module-send": "workspace:*", "@suite-native/module-settings": "workspace:*", + "@suite-native/module-staking-management": "workspace:*", "@suite-native/navigation": "workspace:*", "@suite-native/notifications": "workspace:*", "@suite-native/receive": "workspace:*", diff --git a/suite-native/app/src/navigation/RootStackNavigator.tsx b/suite-native/app/src/navigation/RootStackNavigator.tsx index 1b137c1d5dc6..63080800c500 100644 --- a/suite-native/app/src/navigation/RootStackNavigator.tsx +++ b/suite-native/app/src/navigation/RootStackNavigator.tsx @@ -23,6 +23,7 @@ import { DeviceInfoModalScreen, useHandleDeviceConnection } from '@suite-native/ import { SendStackNavigator } from '@suite-native/module-send'; import { CoinEnablingInitScreen } from '@suite-native/coin-enabling'; import { ConnectPopupScreen, useConnectPopupNavigation } from '@suite-native/module-connect-popup'; +import { StakingDetailScreen } from '@suite-native/module-staking-management'; import { AppTabNavigator } from './AppTabNavigator'; import { useCoinEnablingInitialCheck } from '../hooks/useCoinEnablingInitialCheck'; @@ -73,6 +74,11 @@ export const RootStackNavigator = () => { name={RootStackRoutes.AccountDetail} component={AccountDetailScreen} /> + { return { symbol: account.symbol, fiatValue, - cryptoValue: account.formattedBalance, + cryptoValue: selectAccountCryptoBalanceWithStaking(state, account.key), }; }); diff --git a/suite-native/assets/src/components/AssetItem.tsx b/suite-native/assets/src/components/AssetItem.tsx index 324517742bd9..3c41c163fe6b 100644 --- a/suite-native/assets/src/components/AssetItem.tsx +++ b/suite-native/assets/src/components/AssetItem.tsx @@ -9,7 +9,7 @@ import { useSelectorDeepComparison } from '@suite-common/redux-utils'; import { TokenDefinitionsRootState } from '@suite-common/token-definitions'; import { NetworkSymbol } from '@suite-common/wallet-config'; import { AccountsRootState, DeviceRootState } from '@suite-common/wallet-core'; -import { AccountsListItemBase } from '@suite-native/accounts'; +import { AccountsListItemBase, StakingBadge } from '@suite-native/accounts'; import { Badge, Box, Text } from '@suite-native/atoms'; import { CryptoAmountFormatter, FiatAmountFormatter } from '@suite-native/formatters'; import { Translation } from '@suite-native/intl'; @@ -24,7 +24,6 @@ import { selectHasDeviceAnyTokens } from '@suite-native/tokens'; import { selectHasAnyDeviceAccountsWithStaking, NativeStakingRootState, - StakingBadge, } from '@suite-native/staking'; import { diff --git a/suite-native/assets/src/components/Assets.tsx b/suite-native/assets/src/components/Assets.tsx index 2057bf04f685..70c31cfd9c64 100644 --- a/suite-native/assets/src/components/Assets.tsx +++ b/suite-native/assets/src/components/Assets.tsx @@ -40,12 +40,18 @@ export const Assets = () => { const [selectedAssetSymbol, setSelectedAssetSymbol] = useState(null); const handleSelectAssetsAccount: OnSelectAccount = useCallback( - ({ account, tokenAddress }) => { - navigation.navigate(RootStackRoutes.AccountDetail, { - accountKey: account.key, - tokenContract: tokenAddress, - closeActionType: 'back', - }); + ({ account, tokenAddress, isStaking }) => { + if (isStaking) { + navigation.navigate(RootStackRoutes.StakingDetail, { + accountKey: account.key, + }); + } else { + navigation.navigate(RootStackRoutes.AccountDetail, { + accountKey: account.key, + tokenContract: tokenAddress, + closeActionType: 'back', + }); + } setSelectedAssetSymbol(null); }, [navigation, setSelectedAssetSymbol], diff --git a/suite-native/assets/src/components/NetworkAssetsBottomSheet.tsx b/suite-native/assets/src/components/NetworkAssetsBottomSheet.tsx index ed6be8c3f107..61e7ce6104e7 100644 --- a/suite-native/assets/src/components/NetworkAssetsBottomSheet.tsx +++ b/suite-native/assets/src/components/NetworkAssetsBottomSheet.tsx @@ -24,6 +24,7 @@ export const NetworkAssetsBottomSheet = React.memo( data={items} onClose={onClose} onSelectAccount={onSelectAccount} + isStakingPressable /> ); }, diff --git a/suite-native/assets/tsconfig.json b/suite-native/assets/tsconfig.json index a2c327d159e2..baf9b8a7599b 100644 --- a/suite-native/assets/tsconfig.json +++ b/suite-native/assets/tsconfig.json @@ -31,7 +31,7 @@ { "path": "../intl" }, { "path": "../navigation" }, { "path": "../settings" }, - { "path": "../tokens" }, - { "path": "../../packages/styles" } + { "path": "../staking" }, + { "path": "../tokens" } ] } diff --git a/suite-native/atoms/src/AlertBox.tsx b/suite-native/atoms/src/AlertBox.tsx index 165a87d55c0a..ef71eedfce61 100644 --- a/suite-native/atoms/src/AlertBox.tsx +++ b/suite-native/atoms/src/AlertBox.tsx @@ -1,4 +1,5 @@ import { ReactNode } from 'react'; +import { ActivityIndicator } from 'react-native'; import { prepareNativeStyle, useNativeStyles } from '@trezor/styles'; import { Color, nativeBorders } from '@trezor/theme'; @@ -7,7 +8,7 @@ import { Icon, IconName } from '@suite-common/icons-deprecated'; import { Box } from './Box'; import { Text } from './Text'; -export type AlertBoxVariant = 'info' | 'success' | 'warning' | 'error'; +export type AlertBoxVariant = 'info' | 'success' | 'warning' | 'loading' | 'error'; type AlertBoxStyle = { backgroundColor: Color; @@ -56,6 +57,11 @@ const variantToColorMap = { contentColor: 'iconAlertYellow', borderColor: 'backgroundAlertYellowSubtleOnElevationNegative', }, + loading: { + backgroundColor: 'backgroundAlertYellowSubtleOnElevation1', + contentColor: 'iconAlertYellow', + borderColor: 'backgroundAlertYellowSubtleOnElevationNegative', + }, error: { backgroundColor: 'backgroundAlertRedSubtleOnElevation1', contentColor: 'textAlertRed', @@ -67,6 +73,7 @@ const variantToIconName = { info: 'info', success: 'checkCircle', warning: 'warningTriangle', + loading: 'warningTriangle', error: 'warningCircle', } as const satisfies Record; @@ -76,6 +83,14 @@ export type AlertBoxProps = { borderRadius?: number; }; +const AlertSpinner = ({ color }: { color: Color }) => { + const { + utils: { colors }, + } = useNativeStyles(); + + return ; +}; + export const AlertBox = ({ title, variant = 'info', @@ -92,7 +107,11 @@ export const AlertBox = ({ backgroundColor, })} > - + {variant === 'loading' ? ( + + ) : ( + + )} {title} diff --git a/suite-native/atoms/src/RoundedIcon.tsx b/suite-native/atoms/src/RoundedIcon.tsx index 70cbd2221a84..66a162081724 100644 --- a/suite-native/atoms/src/RoundedIcon.tsx +++ b/suite-native/atoms/src/RoundedIcon.tsx @@ -1,5 +1,3 @@ -import { StyleProp, ViewStyle } from 'react-native'; - import { G } from '@mobily/ts-belt'; import { @@ -15,7 +13,7 @@ import { Color } from '@trezor/theme'; import { Box, BoxProps } from './Box'; -type RoundedIconProps = { +export type RoundedIconProps = { name: IconName | CoinSymbol; color?: Color; iconSize?: IconSize; diff --git a/suite-native/intl/src/en.ts b/suite-native/intl/src/en.ts index f93236641a04..855213c44fac 100644 --- a/suite-native/intl/src/en.ts +++ b/suite-native/intl/src/en.ts @@ -87,6 +87,7 @@ export const en = { accountList: { numberOfTokens: '+{numberOfTokens, plural, one{1 Token} other{# Tokens}}', tokens: 'Tokens', + staking: 'Staking', }, assets: { dashboard: { @@ -941,6 +942,26 @@ export const en = { }, }, }, + staking: { + stakingDetailScreen: { + title: 'Staking', + }, + staked: 'Staked', + rewards: 'Rewards', + apy: 'Annual percentage yield', + stakingCanBeManaged: 'Staking can be currently managed only in', + trezorDesktop: 'Trezor Suite for desktop.', + stakePendingCard: { + totalStakePending: 'Total stake pending', + addingToStakingPool: 'Adding to staking pool', + transactionPending: 'Transaction pending', + unknownStatus: 'Unknown status', + }, + stakingBottomSheet: { + title: 'To manage your staked funds, please use Trezor Suite for desktop.', + description: 'We currently support staking as view-only in Trezor Suite Lite.', + }, + }, }; export type Translations = typeof en; diff --git a/suite-native/module-accounts-management/src/screens/AccountsScreen/AccountsScreen.tsx b/suite-native/module-accounts-management/src/screens/AccountsScreen/AccountsScreen.tsx index 05ff0ca82b91..f3da492a2003 100644 --- a/suite-native/module-accounts-management/src/screens/AccountsScreen/AccountsScreen.tsx +++ b/suite-native/module-accounts-management/src/screens/AccountsScreen/AccountsScreen.tsx @@ -48,6 +48,7 @@ export const AccountsScreen = () => { onSelectAccount={handleSelectAccount} filterValue={accountsFilterValue} hideTokensIntoModal + isStakingPressable /> ); diff --git a/suite-native/module-staking-management/package.json b/suite-native/module-staking-management/package.json new file mode 100644 index 000000000000..00b2c98437f9 --- /dev/null +++ b/suite-native/module-staking-management/package.json @@ -0,0 +1,28 @@ +{ + "name": "@suite-native/module-staking-management", + "version": "1.0.0", + "private": true, + "license": "See LICENSE.md in repo root", + "sideEffects": false, + "main": "src/index", + "scripts": { + "depcheck": "yarn g:depcheck", + "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", + "type-check": "yarn g:tsc --build" + }, + "dependencies": { + "@react-navigation/native": "6.1.18", + "@suite-common/icons-deprecated": "workspace:*", + "@suite-common/wallet-core": "workspace:*", + "@suite-native/atoms": "workspace:*", + "@suite-native/formatters": "workspace:*", + "@suite-native/intl": "workspace:*", + "@suite-native/link": "workspace:*", + "@suite-native/navigation": "workspace:*", + "@suite-native/staking": "workspace:*", + "@trezor/styles": "workspace:*", + "react": "18.2.0", + "react-native": "0.75.2", + "react-redux": "8.0.7" + } +} diff --git a/suite-native/module-staking-management/src/components/StakePendingCard.tsx b/suite-native/module-staking-management/src/components/StakePendingCard.tsx new file mode 100644 index 000000000000..97f0350eb82d --- /dev/null +++ b/suite-native/module-staking-management/src/components/StakePendingCard.tsx @@ -0,0 +1,113 @@ +import { TouchableOpacity } from 'react-native'; +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; + +import { AccountsRootState, selectAccountNetworkSymbol } from '@suite-common/wallet-core'; +import { Box, Card, Text } from '@suite-native/atoms'; +import { CryptoAmountFormatter, CryptoToFiatAmountFormatter } from '@suite-native/formatters'; +import { Translation } from '@suite-native/intl'; +import { prepareNativeStyle, useNativeStyles } from '@trezor/styles'; +import { + selectIsStakeConfirmingByAccountKey, + selectIsStakePendingByAccountKey, + selectTotalStakePendingByAccountKey, +} from '@suite-native/staking'; +import { NativeStakingRootState } from '@suite-native/staking/src/types'; + +const stakingItemStyle = prepareNativeStyle(utils => ({ + flexDirection: 'row', + alignItems: 'center', + gap: utils.spacings.extraSmall, +})); + +const valuesContainerStyle = prepareNativeStyle(utils => ({ + maxWidth: '40%', + flexShrink: 0, + alignItems: 'flex-end', + paddingLeft: utils.spacings.small, +})); + +const getCardAlertProps = (isStakeConfirming: boolean, isStakePending: boolean) => { + if (isStakeConfirming && !isStakePending) { + return { + alertTitle: , + alertVariant: 'loading', + } as const; + } + if (!isStakeConfirming && isStakePending) { + return { + alertTitle: , + alertVariant: 'loading', + } as const; + } + + return { + alertTitle: undefined, + alertVariant: undefined, + } as const; +}; + +type StakePendingCardProps = { + accountKey: string; + handleToggleBottomSheet: (value: boolean) => void; +}; + +export const StakePendingCard = ({ + accountKey, + handleToggleBottomSheet, +}: StakePendingCardProps) => { + const { applyStyle } = useNativeStyles(); + + const networkSymbol = useSelector((state: AccountsRootState) => + selectAccountNetworkSymbol(state, accountKey), + ); + + const totalStakePending = useSelector((state: NativeStakingRootState) => + selectTotalStakePendingByAccountKey(state, accountKey), + ); + + const isStakePending = useSelector((state: NativeStakingRootState) => + selectIsStakePendingByAccountKey(state, accountKey), + ); + const isStakeConfirming = useSelector((state: NativeStakingRootState) => + selectIsStakeConfirmingByAccountKey(state, accountKey), + ); + + const cardAlertProps = useMemo( + () => getCardAlertProps(isStakeConfirming, isStakePending), + [isStakeConfirming, isStakePending], + ); + + if (!networkSymbol || !cardAlertProps.alertVariant) return null; + + return ( + handleToggleBottomSheet(true)}> + + + + + + + + + + + + + + + + + + ); +}; diff --git a/suite-native/module-staking-management/src/components/StakingBalancesOverviewCard.tsx b/suite-native/module-staking-management/src/components/StakingBalancesOverviewCard.tsx new file mode 100644 index 000000000000..ca348369797d --- /dev/null +++ b/suite-native/module-staking-management/src/components/StakingBalancesOverviewCard.tsx @@ -0,0 +1,139 @@ +import { TouchableOpacity } from 'react-native'; +import { useSelector } from 'react-redux'; + +import { Icon } from '@suite-common/icons-deprecated'; +import { AccountsRootState, selectAccountNetworkSymbol } from '@suite-common/wallet-core'; +import { Box, Card, Text } from '@suite-native/atoms'; +import { CryptoAmountFormatter, CryptoToFiatAmountFormatter } from '@suite-native/formatters'; +import { Translation } from '@suite-native/intl'; +import { prepareNativeStyle, useNativeStyles } from '@trezor/styles'; +import { + NativeStakingRootState, + selectAPYByAccountKey, + selectRewardsBalanceByAccountKey, + selectStakedBalanceByAccountKey, +} from '@suite-native/staking'; + +const stakingItemStyle = prepareNativeStyle(utils => ({ + flexDirection: 'row', + alignItems: 'center', + gap: utils.spacings.extraSmall, + paddingBottom: utils.spacings.small, +})); + +const stakingCardStyle = prepareNativeStyle(utils => ({ + marginTop: utils.spacings.medium, +})); + +const stakingWrapperStyle = prepareNativeStyle(utils => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + paddingBottom: utils.spacings.medium, +})); + +const separatorStyle = prepareNativeStyle(utils => ({ + borderBottomWidth: utils.borders.widths.small, + borderBottomColor: utils.colors.borderElevation1, +})); + +type StakingBalancesCardProps = { + accountKey: string; + handleToggleBottomSheet: (value: boolean) => void; +}; + +const CRYPTO_BALANCE_DECIMALS = 5; + +export const StakingBalancesOverviewCard = ({ + accountKey, + handleToggleBottomSheet, +}: StakingBalancesCardProps) => { + const { applyStyle } = useNativeStyles(); + + const networkSymbol = useSelector((state: AccountsRootState) => + selectAccountNetworkSymbol(state, accountKey), + ); + + const apy = useSelector((state: NativeStakingRootState) => + selectAPYByAccountKey(state, accountKey), + ); + + const stakedBalance = useSelector((state: NativeStakingRootState) => + selectStakedBalanceByAccountKey(state, accountKey), + ); + const rewardsBalance = useSelector((state: NativeStakingRootState) => + selectRewardsBalanceByAccountKey(state, accountKey), + ); + + if (!networkSymbol) return null; + + return ( + handleToggleBottomSheet(true)}> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {apy}% + + + + ); +}; diff --git a/suite-native/module-staking-management/src/components/StakingDetailScreenHeader.tsx b/suite-native/module-staking-management/src/components/StakingDetailScreenHeader.tsx new file mode 100644 index 000000000000..4ec0f608053f --- /dev/null +++ b/suite-native/module-staking-management/src/components/StakingDetailScreenHeader.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; + +import { RouteProp, useRoute } from '@react-navigation/native'; + +import { HStack, Text } from '@suite-native/atoms'; +import { + RootStackParamList, + RootStackRoutes, + ScreenSubHeader, + GoBackIcon, +} from '@suite-native/navigation'; +import { Translation } from '@suite-native/intl'; +import { prepareNativeStyle, useNativeStyles } from '@trezor/styles'; +import { Icon } from '@suite-common/icons-deprecated'; +import { AccountsRootState, selectAccountLabel } from '@suite-common/wallet-core'; + +const headerStyle = prepareNativeStyle(utils => ({ + flexShrink: 1, + alignItems: 'center', + gap: utils.spacings.small, +})); + +const headerTextStyle = prepareNativeStyle(() => ({ + flexShrink: 1, +})); + +export const StakingDetailScreenHeader = () => { + const { applyStyle } = useNativeStyles(); + + const route = useRoute>(); + const { accountKey } = route.params; + + const accountLabel = useSelector((state: AccountsRootState) => + selectAccountLabel(state, accountKey), + ); + + return ( + + + + + + + + + {accountLabel} + + + } + leftIcon={} + /> + ); +}; diff --git a/suite-native/module-staking-management/src/components/StakingInfo.tsx b/suite-native/module-staking-management/src/components/StakingInfo.tsx new file mode 100644 index 000000000000..28d500893f71 --- /dev/null +++ b/suite-native/module-staking-management/src/components/StakingInfo.tsx @@ -0,0 +1,77 @@ +import { useCallback, useState } from 'react'; +import { TouchableOpacity } from 'react-native'; + +import { Box, Text } from '@suite-native/atoms'; +import { Translation } from '@suite-native/intl'; +import { useOpenLink } from '@suite-native/link'; +import { prepareNativeStyle, useNativeStyles } from '@trezor/styles'; + +import { StakePendingCard } from './StakePendingCard'; +import { StakingBalancesOverviewCard } from './StakingBalancesOverviewCard'; +import { StakingUnavailableBottomSheet } from './StakingUnavailableBottomSheet'; + +const sectionStyle = prepareNativeStyle(utils => ({ + paddingHorizontal: utils.spacings.small, + paddingVertical: utils.spacings.large, + flex: 1, +})); + +const linkStyle = prepareNativeStyle(() => ({ + textDecorationLine: 'underline', +})); + +type StakingInfoProps = { + accountKey: string; +}; + +export const StakingInfo = ({ accountKey }: StakingInfoProps) => { + const { applyStyle } = useNativeStyles(); + const openLink = useOpenLink(); + + const [isCardSelected, setIsCardSelected] = useState(false); + + const handleDesktopClick = () => { + openLink('https://trezor.io/trezor-suite'); + }; + + const handleToggleBottomSheet = useCallback(() => { + setIsCardSelected(prev => !prev); + }, [setIsCardSelected]); + + return ( + + + + + + + {/* TODO: replace with new icon once we have new package ready */} + {/* */} + + + + + + + + + + + + + + + + + ); +}; diff --git a/suite-native/module-staking-management/src/components/StakingUnavailableBottomSheet.tsx b/suite-native/module-staking-management/src/components/StakingUnavailableBottomSheet.tsx new file mode 100644 index 000000000000..b2c6ab5d2ad5 --- /dev/null +++ b/suite-native/module-staking-management/src/components/StakingUnavailableBottomSheet.tsx @@ -0,0 +1,58 @@ +import { BottomSheet, Button, Text } from '@suite-native/atoms'; +import { Translation } from '@suite-native/intl'; +import { prepareNativeStyle, useNativeStyles } from '@trezor/styles'; + +const bottomSheetElementStyle = prepareNativeStyle(utils => ({ + marginVertical: utils.spacings.small, +})); + +type StakingUnavailableBottomSheet = { + isCardSelected: boolean; + handleToggleBottomSheet: () => void; + handleDesktopClick: () => void; +}; + +export const StakingUnavailableBottomSheet = ({ + isCardSelected, + handleToggleBottomSheet, + handleDesktopClick, +}: StakingUnavailableBottomSheet) => { + const { applyStyle } = useNativeStyles(); + + if (!isCardSelected) return null; + + return ( + + + + } + isVisible + isCloseDisplayed={false} + onClose={handleToggleBottomSheet} + paddingHorizontal="large" + > + + + + + + + + + ); +}; diff --git a/suite-native/module-staking-management/src/index.ts b/suite-native/module-staking-management/src/index.ts new file mode 100644 index 000000000000..669bb9549f96 --- /dev/null +++ b/suite-native/module-staking-management/src/index.ts @@ -0,0 +1 @@ +export { StakingDetailScreen } from './screens/StakingDetailScreen'; diff --git a/suite-native/module-staking-management/src/screens/StakingDetailScreen.tsx b/suite-native/module-staking-management/src/screens/StakingDetailScreen.tsx new file mode 100644 index 000000000000..329915bb77ae --- /dev/null +++ b/suite-native/module-staking-management/src/screens/StakingDetailScreen.tsx @@ -0,0 +1,17 @@ +import { RouteProp, useRoute } from '@react-navigation/native'; + +import { RootStackParamList, RootStackRoutes, Screen } from '@suite-native/navigation'; + +import { StakingDetailScreenHeader } from '../components/StakingDetailScreenHeader'; +import { StakingInfo } from '../components/StakingInfo'; + +export const StakingDetailScreen = () => { + const route = useRoute>(); + const { accountKey } = route.params; + + return ( + }> + + + ); +}; diff --git a/suite-native/module-staking-management/tsconfig.json b/suite-native/module-staking-management/tsconfig.json new file mode 100644 index 000000000000..e2f68e93300a --- /dev/null +++ b/suite-native/module-staking-management/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "libDev" }, + "references": [ + { + "path": "../../suite-common/icons-deprecated" + }, + { + "path": "../../suite-common/wallet-core" + }, + { "path": "../atoms" }, + { "path": "../formatters" }, + { "path": "../intl" }, + { "path": "../link" }, + { "path": "../navigation" }, + { "path": "../staking" }, + { "path": "../../packages/styles" } + ] +} diff --git a/suite-native/navigation/src/navigators.ts b/suite-native/navigation/src/navigators.ts index 6512a3636add..d824302175d4 100644 --- a/suite-native/navigation/src/navigators.ts +++ b/suite-native/navigation/src/navigators.ts @@ -168,6 +168,7 @@ export type RootStackParamList = { }; [RootStackRoutes.DevUtilsStack]: undefined; [RootStackRoutes.AccountDetail]: AccountDetailParams; + [RootStackRoutes.StakingDetail]: { accountKey: AccountKey }; [RootStackRoutes.DeviceInfo]: undefined; [RootStackRoutes.AddCoinAccountStack]: NavigatorScreenParams; [RootStackRoutes.SendStack]: NavigatorScreenParams; diff --git a/suite-native/navigation/src/routes.ts b/suite-native/navigation/src/routes.ts index e1ca4f4b8d5c..fb5cc6741816 100644 --- a/suite-native/navigation/src/routes.ts +++ b/suite-native/navigation/src/routes.ts @@ -4,6 +4,7 @@ export enum RootStackRoutes { AccountsImport = 'AccountsImport', AuthorizeDeviceStack = 'AuthorizeDeviceStack', AccountDetail = 'AccountDetail', + StakingDetail = 'StakingDetail', DevUtilsStack = 'DevUtilsStack', AccountSettings = 'AccountSettings', TransactionDetail = 'TransactionDetail', diff --git a/suite-native/staking/package.json b/suite-native/staking/package.json index 44d434ae6789..52629a60cdf8 100644 --- a/suite-native/staking/package.json +++ b/suite-native/staking/package.json @@ -8,7 +8,6 @@ "scripts": { "depcheck": "yarn g:depcheck", "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", - "test:unit": "jest -c ../../jest.config.base.js", "type-check": "yarn g:tsc --build" }, "dependencies": { diff --git a/suite-native/staking/src/ethereum/ethereumSelectors.ts b/suite-native/staking/src/ethereum/ethereumSelectors.ts new file mode 100644 index 000000000000..55243f25d292 --- /dev/null +++ b/suite-native/staking/src/ethereum/ethereumSelectors.ts @@ -0,0 +1,101 @@ +import { NetworkSymbol } from '@suite-common/wallet-config'; +import { + AccountsRootState, + selectAccountByKey, + selectAccountNetworkSymbol, + selectAccountStakeTransactions, + selectDeviceAccounts, + selectPoolStatsApyData, + StakeRootState, + TransactionsRootState, +} from '@suite-common/wallet-core'; +import { AccountKey } from '@suite-common/wallet-types'; +import { getAccountEverstakeStakingPool, isPending } from '@suite-common/wallet-utils'; + +import { NativeStakingRootState } from '../types'; + +export const selectVisibleDeviceEthereumAccountsWithStakingByNetworkSymbol = ( + state: NativeStakingRootState, + networkSymbol: NetworkSymbol | null, +) => { + const accounts = selectDeviceAccounts(state); + + return accounts.filter( + account => + account.symbol === networkSymbol && + account.visible && + !!getAccountEverstakeStakingPool(account), + ); +}; + +export const selectEthereumStakingPoolByAccountKey = ( + state: AccountsRootState, + accountKey: string, +) => { + const account = selectAccountByKey(state, accountKey); + if (!account) return null; + + return getAccountEverstakeStakingPool(account); +}; + +export const selectEthereumAccountHasStaking = ( + state: NativeStakingRootState, + accountKey: AccountKey, +) => !!selectEthereumStakingPoolByAccountKey(state, accountKey); + +export const selectEthereumIsStakePendingByAccountKey = ( + state: AccountsRootState, + accountKey: string, +) => { + const stakingPool = selectEthereumStakingPoolByAccountKey(state, accountKey); + const isStakePending = Number(stakingPool?.totalPendingStakeBalance ?? 0) > 0; + + return isStakePending; +}; + +export const selectEthereumIsStakeConfirmingByAccountKey = ( + state: TransactionsRootState, + accountKey: string, +) => { + const stakeTxs = selectAccountStakeTransactions(state, accountKey); + const isStakeConfirming = stakeTxs.some(tx => isPending(tx)); + + return isStakeConfirming; +}; + +export const selectEthereumAPYByAccountKey = ( + state: StakeRootState & AccountsRootState, + accountKey: string, +) => { + const networkSymbol = selectAccountNetworkSymbol(state, accountKey); + if (!networkSymbol) return null; + + return selectPoolStatsApyData(state, networkSymbol); +}; + +export const selectEthereumStakedBalanceByAccountKey = ( + state: AccountsRootState, + accountKey: string, +) => { + const stakingPool = selectEthereumStakingPoolByAccountKey(state, accountKey); + + return stakingPool?.depositedBalance ?? '0'; +}; + +export const selectEthereumRewardsBalanceByAccountKey = ( + state: AccountsRootState, + accountKey: string, +) => { + const stakingPool = selectEthereumStakingPoolByAccountKey(state, accountKey); + + return stakingPool?.restakedReward ?? '0'; +}; + +export const selectEthereumTotalStakePendingByAccountKey = ( + state: AccountsRootState, + accountKey: string, +) => { + const stakingPool = selectEthereumStakingPoolByAccountKey(state, accountKey); + + return stakingPool?.totalPendingStakeBalance ?? '0'; +}; diff --git a/suite-native/staking/src/ethereumSelectors.ts b/suite-native/staking/src/ethereumSelectors.ts deleted file mode 100644 index f481c289adfe..000000000000 --- a/suite-native/staking/src/ethereumSelectors.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NetworkSymbol } from '@suite-common/wallet-config'; -import { selectAccountByKey, selectDeviceAccounts } from '@suite-common/wallet-core'; -import { getAccountEverstakeStakingPool } from '@suite-common/wallet-utils'; -import { AccountKey } from '@suite-common/wallet-types'; - -import { NativeStakingRootState } from './types'; - -export const selectVisibleDeviceEthereumAccountsWithStakingByNetworkSymbol = ( - state: NativeStakingRootState, - networkSymbol: NetworkSymbol | null, -) => { - const accounts = selectDeviceAccounts(state); - - return accounts.filter( - account => - account.symbol === networkSymbol && - account.visible && - !!getAccountEverstakeStakingPool(account), - ); -}; - -export const selectEthereumAccountHasStaking = ( - state: NativeStakingRootState, - accountKey: AccountKey, -) => { - const account = selectAccountByKey(state, accountKey); - if (!account) return false; - - return !!getAccountEverstakeStakingPool(account); -}; diff --git a/suite-native/staking/src/index.ts b/suite-native/staking/src/index.ts index 92b25ee74cd1..a29a07b2a855 100644 --- a/suite-native/staking/src/index.ts +++ b/suite-native/staking/src/index.ts @@ -5,4 +5,3 @@ export * from './utils'; export * from './selectors'; export * from './types'; -export * from './components/StakingBadge'; diff --git a/suite-native/staking/src/selectors.ts b/suite-native/staking/src/selectors.ts index ea389f1e44f4..98879c59e012 100644 --- a/suite-native/staking/src/selectors.ts +++ b/suite-native/staking/src/selectors.ts @@ -1,11 +1,18 @@ import { NetworkSymbol } from '@suite-common/wallet-config'; -import { Account, AccountKey } from '@suite-common/wallet-types'; import { selectAccountByKey } from '@suite-common/wallet-core'; +import { Account, AccountKey } from '@suite-common/wallet-types'; +import { getEthereumCryptoBalanceWithStaking } from '@suite-common/wallet-utils'; import { selectEthereumAccountHasStaking, + selectEthereumAPYByAccountKey, + selectEthereumIsStakeConfirmingByAccountKey, + selectEthereumIsStakePendingByAccountKey, + selectEthereumRewardsBalanceByAccountKey, + selectEthereumStakedBalanceByAccountKey, + selectEthereumTotalStakePendingByAccountKey, selectVisibleDeviceEthereumAccountsWithStakingByNetworkSymbol, -} from './ethereumSelectors'; +} from './ethereum/ethereumSelectors'; import { NativeStakingRootState } from './types'; import { doesCoinSupportStaking } from './utils'; @@ -37,27 +44,83 @@ export const selectHasAnyDeviceAccountsWithStaking = ( state: NativeStakingRootState, networkSymbol: NetworkSymbol, ) => { - if (!doesCoinSupportStaking(networkSymbol)) { + return selectDeviceAccountsWithStaking(state, networkSymbol).length > 0; +}; + +export const selectAccountCryptoBalanceWithStaking = ( + state: NativeStakingRootState, + accountKey: AccountKey, +) => { + const account = selectAccountByKey(state, accountKey); + if (!account) return '0'; + + if (!doesCoinSupportStaking(account.symbol)) { + return account.formattedBalance; + } + + switch (account.symbol) { + case 'eth': + case 'thol': + case 'tsep': + return getEthereumCryptoBalanceWithStaking(account); + default: + // This is to make sure that all cases are handled. + account.symbol satisfies never; + + return account.formattedBalance; + } +}; + +export const selectAccountHasStaking = (state: NativeStakingRootState, accountKey: AccountKey) => { + const account = selectAccountByKey(state, accountKey); + const accountSymbol = account?.symbol; + + if (!accountSymbol || !doesCoinSupportStaking(accountSymbol)) { return false; } - switch (networkSymbol) { + switch (accountSymbol) { case 'eth': case 'thol': case 'tsep': - return selectVisibleDeviceEthereumAccountsWithStakingByNetworkSymbol(state, 'eth'); + return selectEthereumAccountHasStaking(state, accountKey); default: // This throws error if any networkSymbol is not handled. - networkSymbol satisfies never; + accountSymbol satisfies never; return false; } }; -export const selectAccountHasStaking = (state: NativeStakingRootState, accountKey: AccountKey) => { +export const selectIsStakePendingByAccountKey = ( + state: NativeStakingRootState, + accountKey: AccountKey, +) => { const account = selectAccountByKey(state, accountKey); const accountSymbol = account?.symbol; + if (!accountSymbol || !doesCoinSupportStaking(accountSymbol)) { + return false; + } + + switch (accountSymbol) { + case 'eth': + case 'thol': + case 'tsep': + return selectEthereumIsStakePendingByAccountKey(state, accountKey); + default: + // This throws error if any networkSymbol is not handled. + accountSymbol satisfies never; + return false; + } +}; + +export const selectIsStakeConfirmingByAccountKey = ( + state: NativeStakingRootState, + accountKey: AccountKey, +) => { + const account = selectAccountByKey(state, accountKey); + const accountSymbol = account?.symbol; if (!accountSymbol || !doesCoinSupportStaking(accountSymbol)) { return false; } @@ -66,7 +129,7 @@ export const selectAccountHasStaking = (state: NativeStakingRootState, accountKe case 'eth': case 'thol': case 'tsep': - return selectEthereumAccountHasStaking(state, accountKey); + return selectEthereumIsStakeConfirmingByAccountKey(state, accountKey); default: // This throws error if any networkSymbol is not handled. accountSymbol satisfies never; @@ -74,3 +137,92 @@ export const selectAccountHasStaking = (state: NativeStakingRootState, accountKe return false; } }; + +export const selectAPYByAccountKey = (state: NativeStakingRootState, accountKey: AccountKey) => { + const account = selectAccountByKey(state, accountKey); + const accountSymbol = account?.symbol; + if (!accountSymbol || !doesCoinSupportStaking(accountSymbol)) { + return null; + } + + switch (accountSymbol) { + case 'eth': + case 'thol': + case 'tsep': + return selectEthereumAPYByAccountKey(state, accountKey); + default: + // This throws error if any networkSymbol is not handled. + accountSymbol satisfies never; + + return null; + } +}; + +export const selectStakedBalanceByAccountKey = ( + state: NativeStakingRootState, + accountKey: AccountKey, +) => { + const account = selectAccountByKey(state, accountKey); + const accountSymbol = account?.symbol; + if (!accountSymbol || !doesCoinSupportStaking(accountSymbol)) { + return '0'; + } + + switch (accountSymbol) { + case 'eth': + case 'thol': + case 'tsep': + return selectEthereumStakedBalanceByAccountKey(state, accountKey); + default: + // This throws error if any networkSymbol is not handled. + accountSymbol satisfies never; + + return '0'; + } +}; + +export const selectRewardsBalanceByAccountKey = ( + state: NativeStakingRootState, + accountKey: AccountKey, +) => { + const account = selectAccountByKey(state, accountKey); + const accountSymbol = account?.symbol; + if (!accountSymbol || !doesCoinSupportStaking(accountSymbol)) { + return '0'; + } + + switch (accountSymbol) { + case 'eth': + case 'thol': + case 'tsep': + return selectEthereumRewardsBalanceByAccountKey(state, accountKey); + default: + // This throws error if any networkSymbol is not handled. + accountSymbol satisfies never; + + return '0'; + } +}; + +export const selectTotalStakePendingByAccountKey = ( + state: NativeStakingRootState, + accountKey: AccountKey, +) => { + const account = selectAccountByKey(state, accountKey); + const accountSymbol = account?.symbol; + if (!accountSymbol || !doesCoinSupportStaking(accountSymbol)) { + return '0'; + } + + switch (accountSymbol) { + case 'eth': + case 'thol': + case 'tsep': + return selectEthereumTotalStakePendingByAccountKey(state, accountKey); + default: + // This throws error if any networkSymbol is not handled. + accountSymbol satisfies never; + + return '0'; + } +}; diff --git a/suite-native/staking/src/types.ts b/suite-native/staking/src/types.ts index 4edf0ad802d4..883b4bc0bcdf 100644 --- a/suite-native/staking/src/types.ts +++ b/suite-native/staking/src/types.ts @@ -1,3 +1,11 @@ -import { AccountsRootState, DeviceRootState } from '@suite-common/wallet-core'; +import { + AccountsRootState, + DeviceRootState, + StakeRootState, + TransactionsRootState, +} from '@suite-common/wallet-core'; -export type NativeStakingRootState = AccountsRootState & DeviceRootState; +export type NativeStakingRootState = AccountsRootState & + DeviceRootState & + StakeRootState & + TransactionsRootState; diff --git a/suite-native/staking/src/utils.ts b/suite-native/staking/src/utils.ts index 169d5a7b5e70..bce1cb00327b 100644 --- a/suite-native/staking/src/utils.ts +++ b/suite-native/staking/src/utils.ts @@ -1,6 +1,4 @@ import { NetworkSymbol } from '@suite-common/wallet-config'; -import { Account } from '@suite-common/wallet-types'; -import { getEthereumCryptoBalanceWithStaking } from '@suite-common/wallet-utils'; const stakingCoins = ['eth', 'thol', 'tsep'] as const satisfies NetworkSymbol[]; type NetworkSymbolWithStaking = (typeof stakingCoins)[number]; @@ -10,21 +8,3 @@ export const doesCoinSupportStaking = ( ): symbol is NetworkSymbolWithStaking => { return stakingCoins.includes(symbol as any); }; - -export const getCryptoBalanceWithStaking = (account: Account) => { - if (!doesCoinSupportStaking(account.symbol)) { - return account.formattedBalance; - } - - switch (account.symbol) { - case 'eth': - case 'thol': - case 'tsep': - return getEthereumCryptoBalanceWithStaking(account); - default: - // This is to make sure that all cases are handled. - account.symbol satisfies never; - - return account.formattedBalance; - } -}; diff --git a/suite-native/staking/tsconfig.json b/suite-native/staking/tsconfig.json index c7ebe855e214..5732d803b074 100644 --- a/suite-native/staking/tsconfig.json +++ b/suite-native/staking/tsconfig.json @@ -1,5 +1,18 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "libDev" }, - "references": [] + "references": [ + { + "path": "../../suite-common/wallet-config" + }, + { + "path": "../../suite-common/wallet-core" + }, + { + "path": "../../suite-common/wallet-types" + }, + { + "path": "../../suite-common/wallet-utils" + } + ] } diff --git a/tsconfig.json b/tsconfig.json index 6f4d9e835aec..b93626b2dccd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -98,6 +98,9 @@ { "path": "suite-native/module-settings" }, + { + "path": "suite-native/module-staking-management" + }, { "path": "suite-native/navigation" }, { "path": "suite-native/notifications" }, { "path": "suite-native/qr-code" }, @@ -106,6 +109,7 @@ }, { "path": "suite-native/receive" }, { "path": "suite-native/settings" }, + { "path": "suite-native/staking" }, { "path": "suite-native/state" }, { "path": "suite-native/storage" }, { "path": "suite-native/test-utils" }, diff --git a/yarn.lock b/yarn.lock index 81b6e9f76cc2..b88ff82a8aed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9488,6 +9488,8 @@ __metadata: "@suite-native/intl": "workspace:*" "@suite-native/navigation": "workspace:*" "@suite-native/settings": "workspace:*" + "@suite-native/staking": "workspace:*" + "@suite-native/toasts": "workspace:*" "@suite-native/tokens": "workspace:*" "@trezor/styles": "workspace:*" jotai: "npm:1.9.1" @@ -9583,6 +9585,7 @@ __metadata: "@suite-native/module-receive": "workspace:*" "@suite-native/module-send": "workspace:*" "@suite-native/module-settings": "workspace:*" + "@suite-native/module-staking-management": "workspace:*" "@suite-native/navigation": "workspace:*" "@suite-native/notifications": "workspace:*" "@suite-native/receive": "workspace:*" @@ -9663,8 +9666,8 @@ __metadata: "@suite-native/intl": "workspace:*" "@suite-native/navigation": "workspace:*" "@suite-native/settings": "workspace:*" + "@suite-native/staking": "workspace:*" "@suite-native/tokens": "workspace:*" - "@trezor/styles": "workspace:*" proxy-memoize: "npm:2.0.2" react: "npm:^18.2.0" react-native: "npm:0.75.2" @@ -10413,6 +10416,26 @@ __metadata: languageName: unknown linkType: soft +"@suite-native/module-staking-management@workspace:*, @suite-native/module-staking-management@workspace:suite-native/module-staking-management": + version: 0.0.0-use.local + resolution: "@suite-native/module-staking-management@workspace:suite-native/module-staking-management" + dependencies: + "@react-navigation/native": "npm:6.1.18" + "@suite-common/icons-deprecated": "workspace:*" + "@suite-common/wallet-core": "workspace:*" + "@suite-native/atoms": "workspace:*" + "@suite-native/formatters": "workspace:*" + "@suite-native/intl": "workspace:*" + "@suite-native/link": "workspace:*" + "@suite-native/navigation": "workspace:*" + "@suite-native/staking": "workspace:*" + "@trezor/styles": "workspace:*" + react: "npm:18.2.0" + react-native: "npm:0.75.2" + react-redux: "npm:8.0.7" + languageName: unknown + linkType: soft + "@suite-native/navigation@workspace:*, @suite-native/navigation@workspace:suite-native/navigation": version: 0.0.0-use.local resolution: "@suite-native/navigation@workspace:suite-native/navigation" @@ -10551,7 +10574,7 @@ __metadata: languageName: unknown linkType: soft -"@suite-native/staking@workspace:suite-native/staking": +"@suite-native/staking@workspace:*, @suite-native/staking@workspace:suite-native/staking": version: 0.0.0-use.local resolution: "@suite-native/staking@workspace:suite-native/staking" dependencies: