From 354f5814675f2758f7dd3b5e0f785788d4cb9dd4 Mon Sep 17 00:00:00 2001 From: Max Voloshinskii Date: Tue, 26 Mar 2024 16:48:54 +0300 Subject: [PATCH 01/11] fix(mobile): Restake banner hotfix for non-whitelisted pools --- packages/mobile/index.js | 15 ++++---- .../RestakeBanner/RestakeBanner.tsx | 23 ++++++++----- .../src/core/Notifications/Notification.tsx | 6 +++- packages/mobile/src/core/Staking/Staking.tsx | 32 ++++++++++------- .../src/core/StakingSend/StakingSend.tsx | 32 ++++++++++++----- .../src/store/zustand/notifications/types.ts | 9 +++++ .../notifications/useNotificationsStore.ts | 34 +++++++++++++++++++ .../src/wallet/managers/StakingManager.ts | 23 ------------- 8 files changed, 114 insertions(+), 60 deletions(-) diff --git a/packages/mobile/index.js b/packages/mobile/index.js index 6b6d6dad9..d5b3354c7 100644 --- a/packages/mobile/index.js +++ b/packages/mobile/index.js @@ -22,7 +22,6 @@ import crashlytics from '@react-native-firebase/crashlytics'; import messaging from '@react-native-firebase/messaging'; import { withIAPContext } from 'react-native-iap'; import { startApp } from './src/index'; -import { tk } from './src/wallet'; LogBox.ignoreLogs([ 'Non-serializable values were found in the navigation state', @@ -47,15 +46,17 @@ async function handleDappMessage(remoteMessage) { ) { return null; } - + await useNotificationsStore.persist.rehydrate(); if (remoteMessage.data?.type === 'better_stake_option_found') { - tk.wallet.staking.toggleRestakeBanner( - true, - remoteMessage.data.stakingAddressToMigrateFrom, - ); + useNotificationsStore + .getState() + .actions.toggleRestakeBanner( + remoteMessage.data.account, + true, + remoteMessage.data.stakingAddressToMigrateFrom, + ); } - await useNotificationsStore.persist.rehydrate(); useNotificationsStore.getState().actions.addNotification( { ...remoteMessage.data, diff --git a/packages/mobile/src/components/RestakeBanner/RestakeBanner.tsx b/packages/mobile/src/components/RestakeBanner/RestakeBanner.tsx index 3ab76b3d2..29f74e041 100644 --- a/packages/mobile/src/components/RestakeBanner/RestakeBanner.tsx +++ b/packages/mobile/src/components/RestakeBanner/RestakeBanner.tsx @@ -26,6 +26,7 @@ import { IconsComposition } from './IconsComposition'; import { useFiatValue } from '$hooks/useFiatValue'; import { CryptoCurrencies } from '$shared/constants'; import { stakingFormatter } from '$utils/formatter'; +import { useNotificationsStore } from '$store'; export interface ExtendedPoolInfo extends PoolInfo { isWithdrawal: boolean; @@ -35,6 +36,7 @@ export interface ExtendedPoolInfo extends PoolInfo { export interface RestakeBannerProps { poolsList: ExtendedPoolInfo[]; migrateFrom: string; + bypassUnstakeStep?: boolean; } export enum RestakeSteps { @@ -52,11 +54,14 @@ export const RestakeBanner = memo((props) => { ) as ExtendedPoolInfo; }, [props.poolsList]); const { handleTopUpPress } = usePoolInfo(tonstakersPool); + const toggleRestakeBanner = useNotificationsStore( + (state) => state.actions.toggleRestakeBanner, + ); const handleCloseRestakeBanner = useCallback(() => { LayoutAnimation.easeInEaseOut(); - tk.wallet.staking.toggleRestakeBanner(false); - }, []); + toggleRestakeBanner(tk.wallet.address.ton.raw, false); + }, [toggleRestakeBanner]); const poolToWithdrawal = useMemo( () => @@ -68,8 +73,6 @@ export const RestakeBanner = memo((props) => { [poolToWithdrawal], ); - const bypassStakeStep = useStakingState((s) => s.bypassStakeStep, []); - const readyWithdraw = useFiatValue( CryptoCurrencies.Ton, stakingFormatter.fromNano(toWithdrawalStakingInfo?.ready_withdraw ?? '0'), @@ -107,7 +110,11 @@ export const RestakeBanner = memo((props) => { return RestakeSteps.DONE; } // Go to last step if pool to withdrawal is empty now (or if balance so small, or step is bypassed) - if (bypassStakeStep || Number(poolToWithdrawal?.balance) < 0.1) { + if ( + props.bypassUnstakeStep || + !poolToWithdrawal?.balance || + Number(poolToWithdrawal?.balance) < 0.1 + ) { return RestakeSteps.STAKE_INTO_TONSTAKERS; } // If user has pending withdrawal, render step with waiting @@ -116,7 +123,7 @@ export const RestakeBanner = memo((props) => { } return RestakeSteps.UNSTAKE; }, [ - bypassStakeStep, + props.bypassUnstakeStep, isWaitingForWithdrawal, poolToWithdrawal?.balance, readyWithdraw.amount, @@ -130,8 +137,8 @@ export const RestakeBanner = memo((props) => { }, [currentStepId, handleCloseRestakeBanner]); const { formattedDuration, isCooldown } = useStakingCycle( - poolToWithdrawal?.cycle_start, - poolToWithdrawal?.cycle_end, + poolToWithdrawal?.cycle_start ?? Date.now(), + poolToWithdrawal?.cycle_end ?? Date.now(), isWaitingForWithdrawal, ); diff --git a/packages/mobile/src/core/Notifications/Notification.tsx b/packages/mobile/src/core/Notifications/Notification.tsx index 64d33236b..15afa856b 100644 --- a/packages/mobile/src/core/Notifications/Notification.tsx +++ b/packages/mobile/src/core/Notifications/Notification.tsx @@ -6,6 +6,7 @@ import { disableNotifications, useConnectedAppsList, useDAppsNotifications, + useNotificationsStore, } from '$store'; import { format, getDomainFromURL } from '$utils'; import { Swipeable, TouchableOpacity } from 'react-native-gesture-handler'; @@ -54,6 +55,9 @@ export const Notification: React.FC = (props) => { const { showActionSheetWithOptions } = useActionSheet(); const { deleteNotificationByReceivedAt } = useDAppsNotifications(); const listItemRef = useRef(null); + const toggleRestakeBanner = useNotificationsStore( + (state) => state.actions.toggleRestakeBanner, + ); const handleDelete = useCallback(() => { deleteNotificationByReceivedAt(props.notification.received_at); @@ -141,7 +145,7 @@ export const Notification: React.FC = (props) => { const handleOpenInWebView = useCallback(() => { if (props.notification.type === NotificationType.BETTER_STAKE_OPTION_FOUND) { - tk.wallet.staking.toggleRestakeBanner(true); + toggleRestakeBanner(tk.wallet.address.ton.raw, true); } if (!props.notification.link && !props.notification.deeplink) { diff --git a/packages/mobile/src/core/Staking/Staking.tsx b/packages/mobile/src/core/Staking/Staking.tsx index 47dbd7dff..dec0220fa 100644 --- a/packages/mobile/src/core/Staking/Staking.tsx +++ b/packages/mobile/src/core/Staking/Staking.tsx @@ -2,7 +2,7 @@ import { useStakingRefreshControl } from '$hooks/useStakingRefreshControl'; import { useNavigation } from '@tonkeeper/router'; import { MainStackRouteNames, openDAppBrowser } from '$navigation'; import { StakingListCell } from '$shared/components'; -import { FlashCountKeys, useFlashCount } from '$store'; +import { FlashCountKeys, useFlashCount, useNotificationsStore } from '$store'; import { Button, Icon, ScrollHandler, Spacer, Text } from '$uikit'; import { List } from '$uikit/List/old/List'; import { getImplementationIcon, getPoolIcon } from '$utils/staking'; @@ -24,6 +24,8 @@ import { useBalancesState, useJettons, useStakingState } from '@tonkeeper/shared import { StakingManager, StakingProvider } from '$wallet/managers/StakingManager'; import { config } from '$config'; import { RestakeBanner } from '../../components/RestakeBanner/RestakeBanner'; +import { shallow } from 'zustand/shallow'; +import { tk } from '$wallet'; interface Props {} @@ -36,9 +38,11 @@ export const Staking: FC = () => { const pools = useStakingState((s) => s.pools); const stakingInfo = useStakingState((s) => s.stakingInfo); const highestApyPool = useStakingState((s) => s.highestApyPool); - const showRestakeBanner = useStakingState((s) => s.showRestakeBanner); - const stakingAddressToMigrateFrom = useStakingState( - (s) => s.stakingAddressToMigrateFrom, + + const rawAddress = tk.wallet.address.ton.raw ?? ''; + const notificationsStore = useNotificationsStore( + (state) => state.wallets[rawAddress], + shallow, ); const [flashShownCount] = useFlashCount(FlashCountKeys.Staking); @@ -221,15 +225,17 @@ export const Staking: FC = () => { showsVerticalScrollIndicator={false} > - {showRestakeBanner && stakingAddressToMigrateFrom && ( - <> - - - - )} + {notificationsStore?.showRestakeBanner && + notificationsStore?.stakingAddressToMigrateFrom && ( + <> + + + + )} {!hasActivePools ? ( {t('staking.title_large')} diff --git a/packages/mobile/src/core/StakingSend/StakingSend.tsx b/packages/mobile/src/core/StakingSend/StakingSend.tsx index 826b9f396..c478febcb 100644 --- a/packages/mobile/src/core/StakingSend/StakingSend.tsx +++ b/packages/mobile/src/core/StakingSend/StakingSend.tsx @@ -9,7 +9,7 @@ import { AppStackRouteNames } from '$navigation'; import { AppStackParamList } from '$navigation/AppStack'; import { StepView, StepViewItem, StepViewRef } from '$shared/components'; import { CryptoCurrencies, Decimals } from '$shared/constants'; -import { Toast } from '$store'; +import { Toast, useNotificationsStore } from '$store'; import { getStakingPoolByAddress } from '@tonkeeper/shared/utils/staking'; import { walletWalletSelector } from '$store/wallet'; import { NavBar } from '$uikit'; @@ -38,6 +38,7 @@ import { SignRawMessage } from '$core/ModalContainer/NFTOperations/TXRequest.typ import { useStakingState, useWallet } from '@tonkeeper/shared/hooks'; import { tk } from '$wallet'; import { Address } from '@tonkeeper/shared/Address'; +import { shallow } from 'zustand/shallow'; interface Props { route: RouteProp; @@ -176,6 +177,15 @@ export const StakingSend: FC = (props) => { const messages = useRef([]); + const rawAddress = wallet.address.ton.raw ?? ''; + const stakingAddressToMigrateFrom = useNotificationsStore( + (state) => state.wallets[rawAddress]?.stakingAddressToMigrateFrom, + shallow, + ); + const bypassUnstakeStep = useNotificationsStore( + (state) => state.actions.bypassUnstakeStep, + ); + const { isLiquidJetton, price } = useCurrencyToSend(currency, tokenType); const parsedAmount = useMemo(() => { @@ -336,20 +346,26 @@ export const StakingSend: FC = (props) => { await actionRef.current.send(privateKey); if ( isWithdrawalConfrim && - tk.wallet.staking.state.data.stakingAddressToMigrateFrom && - Address.compare( - tk.wallet.staking.state.data.stakingAddressToMigrateFrom, - pool.address, - ) + stakingAddressToMigrateFrom && + Address.compare(stakingAddressToMigrateFrom, pool.address) ) { - tk.wallet.staking.setBypassStakeStep(); + bypassUnstakeStep(rawAddress); } } catch (e) { throw e; } finally { setSending(false); } - }, [isDeposit, isWithdrawalConfrim, pool, totalFee, unlockVault]); + }, [ + bypassUnstakeStep, + isDeposit, + isWithdrawalConfrim, + pool, + rawAddress, + stakingAddressToMigrateFrom, + totalFee, + unlockVault, + ]); useEffect(() => { if (isWithdrawalConfrim || initialAmount) { diff --git a/packages/mobile/src/store/zustand/notifications/types.ts b/packages/mobile/src/store/zustand/notifications/types.ts index 33119a0c3..97a8536d4 100644 --- a/packages/mobile/src/store/zustand/notifications/types.ts +++ b/packages/mobile/src/store/zustand/notifications/types.ts @@ -20,6 +20,9 @@ export interface INotificationsState { last_seen: number; last_seen_activity_screen: number; should_show_red_dot: boolean; + showRestakeBanner?: boolean; + stakingAddressToMigrateFrom?: string; + bypassUnstakeStep?: boolean; } export interface INotificationsStore { @@ -35,5 +38,11 @@ export interface INotificationsStore { deleteNotificationByReceivedAt: (receivedAt: number, rawAddress: string) => void; removeNotificationsByDappUrl: (dapp_url: string, rawAddress: string) => void; removeRedDot: (rawAddress: string) => void; + toggleRestakeBanner: ( + rawAddress: string, + showRestakeBanner: boolean, + stakingAddressToMigrateFrom?: string, + ) => void; + bypassUnstakeStep: (rawAddress: string) => void; }; } diff --git a/packages/mobile/src/store/zustand/notifications/useNotificationsStore.ts b/packages/mobile/src/store/zustand/notifications/useNotificationsStore.ts index e59ef6da8..8b4769b2d 100644 --- a/packages/mobile/src/store/zustand/notifications/useNotificationsStore.ts +++ b/packages/mobile/src/store/zustand/notifications/useNotificationsStore.ts @@ -110,6 +110,40 @@ export const useNotificationsStore = create( }; }); }, + toggleRestakeBanner: ( + rawAddress: string, + showRestakeBanner: boolean, + stakingAddressToMigrateFrom?: string, + ) => { + set((state) => { + const wallet = state.wallets[rawAddress]; + return { + wallets: { + ...state.wallets, + [rawAddress]: { + ...wallet, + showRestakeBanner, + bypassUnstakeStep: false, + ...(stakingAddressToMigrateFrom ? { stakingAddressToMigrateFrom } : {}), + }, + }, + }; + }); + }, + bypassUnstakeStep: (rawAddress) => { + set((state) => { + const wallet = state.wallets[rawAddress]; + return { + wallets: { + ...state.wallets, + [rawAddress]: { + ...wallet, + bypassUnstakeStep: true, + }, + }, + }; + }); + }, reset: () => set(initialState), }, }), diff --git a/packages/mobile/src/wallet/managers/StakingManager.ts b/packages/mobile/src/wallet/managers/StakingManager.ts index ccc7bf097..72af2fef4 100644 --- a/packages/mobile/src/wallet/managers/StakingManager.ts +++ b/packages/mobile/src/wallet/managers/StakingManager.ts @@ -40,9 +40,6 @@ export type StakingState = { stakingJettons: Record; stakingJettonsUpdatedAt: number; stakingBalance: string; - showRestakeBanner: boolean; - stakingAddressToMigrateFrom?: string; - bypassStakeStep?: boolean; }; export class StakingManager { @@ -61,8 +58,6 @@ export class StakingManager { providers: [], highestApyPool: null, stakingBalance: '0', - showRestakeBanner: false, - bypassStakeStep: false, }; static calculatePoolBalance(pool: PoolInfo, stakingInfo: StakingInfo) { @@ -281,24 +276,6 @@ export class StakingManager { } } - public toggleRestakeBanner( - showRestakeBanner: boolean, - stakingAddressToMigrateFrom?: string, - ) { - if (stakingAddressToMigrateFrom) { - return this.state.set({ - stakingAddressToMigrateFrom, - showRestakeBanner, - bypassStakeStep: false, - }); - } - this.state.set({ showRestakeBanner, bypassStakeStep: false }); - } - - public setBypassStakeStep() { - this.state.set({ bypassStakeStep: true }); - } - public async reload() { await this.load(); } From 26603d1aeb3fddc42d34d4868724c97de9fff9dc Mon Sep 17 00:00:00 2001 From: Max Voloshinskii Date: Tue, 26 Mar 2024 16:49:41 +0300 Subject: [PATCH 02/11] fix(mobile): account delete feature resets only current wallet --- .../mobile/src/core/DeleteAccountDone/DeleteAccountDone.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mobile/src/core/DeleteAccountDone/DeleteAccountDone.tsx b/packages/mobile/src/core/DeleteAccountDone/DeleteAccountDone.tsx index 98f0d95bb..3b6abc06d 100644 --- a/packages/mobile/src/core/DeleteAccountDone/DeleteAccountDone.tsx +++ b/packages/mobile/src/core/DeleteAccountDone/DeleteAccountDone.tsx @@ -22,7 +22,7 @@ export const DeleteAccountDone: React.FC = () => { }, []); const handleAnimationEnd = useCallback(() => { - dispatch(walletActions.cleanWallet({ cleanAll: true })); + dispatch(walletActions.cleanWallet({ cleanAll: false })); }, [dispatch]); return ( From 172d1e260ecec58d3fbac22e374b6c8d4d582799 Mon Sep 17 00:00:00 2001 From: Max Voloshinskii Date: Tue, 26 Mar 2024 16:50:30 +0300 Subject: [PATCH 03/11] bump(mobile): 4.1.1 --- packages/mobile/android/app/build.gradle | 2 +- packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mobile/android/app/build.gradle b/packages/mobile/android/app/build.gradle index 8af57145e..b36ee490c 100644 --- a/packages/mobile/android/app/build.gradle +++ b/packages/mobile/android/app/build.gradle @@ -92,7 +92,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 433 - versionName "4.1.0" + versionName "4.1.1" missingDimensionStrategy 'react-native-camera', 'general' missingDimensionStrategy 'store', 'play' } diff --git a/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj b/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj index 5bffe413f..a5a93362c 100644 --- a/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj +++ b/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj @@ -1294,7 +1294,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.1.0; + MARKETING_VERSION = 4.1.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1328,7 +1328,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.1.0; + MARKETING_VERSION = 4.1.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", From 19e93fce6b1a1b8cf02ecbae4647a560f793d6ab Mon Sep 17 00:00:00 2001 From: Max Voloshinskii Date: Tue, 26 Mar 2024 18:14:49 +0300 Subject: [PATCH 04/11] fix(mobile): bypass unstake step if it's cooldown now too --- packages/mobile/src/core/StakingSend/StakingSend.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/mobile/src/core/StakingSend/StakingSend.tsx b/packages/mobile/src/core/StakingSend/StakingSend.tsx index c478febcb..46d565839 100644 --- a/packages/mobile/src/core/StakingSend/StakingSend.tsx +++ b/packages/mobile/src/core/StakingSend/StakingSend.tsx @@ -344,8 +344,12 @@ export const StakingSend: FC = (props) => { const privateKey = await vault.getTonPrivateKey(); await actionRef.current.send(privateKey); + + const endTimestamp = pool.cycle_end * 1000; + const isCooldown = Date.now() > endTimestamp; + if ( - isWithdrawalConfrim && + (isWithdrawalConfrim || isCooldown) && stakingAddressToMigrateFrom && Address.compare(stakingAddressToMigrateFrom, pool.address) ) { From f76e1753a5f178d9134bc9963843887f17a91de5 Mon Sep 17 00:00:00 2001 From: Max Voloshinskii Date: Tue, 26 Mar 2024 18:21:53 +0300 Subject: [PATCH 05/11] fix(mobile): Untake all for TF pool --- .../RestakeBanner/RestakeBanner.tsx | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/mobile/src/components/RestakeBanner/RestakeBanner.tsx b/packages/mobile/src/components/RestakeBanner/RestakeBanner.tsx index 29f74e041..302f802d7 100644 --- a/packages/mobile/src/components/RestakeBanner/RestakeBanner.tsx +++ b/packages/mobile/src/components/RestakeBanner/RestakeBanner.tsx @@ -82,13 +82,17 @@ export const RestakeBanner = memo((props) => { const handleWithdrawal = useCallback( (pool: ExtendedPoolInfo, withdrawAll?: boolean) => () => { + if (pool.implementation === PoolImplementationType.Tf) { + nav.push(AppStackRouteNames.StakingSend, { + poolAddress: pool.address, + transactionType: StakingTransactionType.WITHDRAWAL_CONFIRM, + }); + return; + } nav.push(AppStackRouteNames.StakingSend, { amount: withdrawAll && pool.balance, poolAddress: pool.address, - transactionType: - pool.implementation === PoolImplementationType.Tf - ? StakingTransactionType.WITHDRAWAL_CONFIRM - : StakingTransactionType.WITHDRAWAL, + transactionType: StakingTransactionType.WITHDRAWAL, }); }, [nav], @@ -202,12 +206,14 @@ export const RestakeBanner = memo((props) => { }), })} />, -