Skip to content

Commit

Permalink
feat(mobile): staking improvements (#640)
Browse files Browse the repository at this point in the history
  • Loading branch information
sorokin0andrey authored Nov 30, 2023
1 parent a97c1f7 commit 807a055
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,19 @@ export interface InsufficientFundsParams {
currency?: string;
stakingFee?: string;
fee?: string;
isStakingDeposit?: boolean;
}

export const InsufficientFundsModal = memo<InsufficientFundsParams>((props) => {
const { totalAmount, balance, currency = 'TON', decimals = 9, stakingFee, fee } = props;
const {
totalAmount,
balance,
currency = 'TON',
decimals = 9,
stakingFee,
fee,
isStakingDeposit,
} = props;
const nav = useNavigation();
const formattedAmount = useMemo(
() => formatter.format(fromNano(totalAmount, decimals), { decimals }),
Expand All @@ -58,6 +67,48 @@ export const InsufficientFundsModal = memo<InsufficientFundsParams>((props) => {
openExploreTab('defi');
}, [nav]);

const content = useMemo(() => {
if (isStakingDeposit) {
return (
<Text variant="body1" color="foregroundSecondary" textAlign="center">
{t('txActions.signRaw.insufficientFunds.stakingDeposit', {
amount: formattedAmount,
currency,
})}
{t('txActions.signRaw.insufficientFunds.yourBalance', {
balance: formattedBalance,
currency,
})}
</Text>
);
}

if (stakingFee && fee) {
return (
<Text variant="body1" color="foregroundSecondary" textAlign="center">
{t('txActions.signRaw.insufficientFunds.stakingFee', {
count: Number(stakingFee),
fee,
})}
</Text>
);
}

return (
<Text variant="body1" color="foregroundSecondary" textAlign="center">
{t('txActions.signRaw.insufficientFunds.toBePaid', {
amount: formattedAmount,
currency,
})}
{currency === 'TON' && t('txActions.signRaw.insufficientFunds.withFees')}
{t('txActions.signRaw.insufficientFunds.yourBalance', {
balance: formattedBalance,
currency,
})}
</Text>
);
}, [currency, fee, formattedAmount, formattedBalance, isStakingDeposit, stakingFee]);

return (
<Modal>
<Modal.Header />
Expand All @@ -67,26 +118,7 @@ export const InsufficientFundsModal = memo<InsufficientFundsParams>((props) => {
<Text textAlign="center" variant="h2" style={{ marginBottom: 4 }}>
{t('txActions.signRaw.insufficientFunds.title')}
</Text>
{stakingFee && fee ? (
<Text variant="body1" color="foregroundSecondary" textAlign="center">
{t('txActions.signRaw.insufficientFunds.stakingFee', {
count: Number(stakingFee),
fee,
})}
</Text>
) : (
<Text variant="body1" color="foregroundSecondary" textAlign="center">
{t('txActions.signRaw.insufficientFunds.toBePaid', {
amount: formattedAmount,
currency,
})}
{currency === 'TON' && t('txActions.signRaw.insufficientFunds.withFees')}
{t('txActions.signRaw.insufficientFunds.yourBalance', {
balance: formattedBalance,
currency,
})}
</Text>
)}
{content}
</S.Wrap>
</Modal.Content>
<Modal.Footer>
Expand Down
41 changes: 39 additions & 2 deletions packages/mobile/src/core/Staking/Staking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { t } from '@tonkeeper/shared/i18n';
import { Address } from '@tonkeeper/shared/Address';
import { PoolImplementationType } from '@tonkeeper/core/src/TonAPI';
import { walletSelector } from '$store/wallet';
import { CryptoCurrencies, getServerConfig } from '$shared/constants';
import { CryptoCurrencies, Decimals, getServerConfig } from '$shared/constants';
import { Flash } from '@tonkeeper/uikit';
import { Ton } from '$libs/Ton';

Expand Down Expand Up @@ -142,8 +142,45 @@ export const Staking: FC<Props> = () => {
openDAppBrowser(getServerConfig('stakingInfoUrl'));
}, []);

const otherPoolsEstimation = useMemo(() => {
const otherPools = activePools.filter(
(pool) => pool.implementation !== PoolImplementationType.LiquidTF,
);

return otherPools.reduce(
(acc, pool) => {
return {
balance: new BigNumber(acc.balance)
.plus(pool.balance || '0')
.decimalPlaces(Decimals[CryptoCurrencies.Ton])
.toString(),
estimatedProfit: new BigNumber(pool.balance || '0')
.multipliedBy(new BigNumber(pool.apy).dividedBy(100))
.plus(acc.estimatedProfit)
.decimalPlaces(Decimals[CryptoCurrencies.Ton])
.toString(),
};
},
{ balance: '0', estimatedProfit: '0' },
);
}, [activePools]);

const getEstimateProfitMessage = useCallback(
(provider: StakingProvider) => {
if (new BigNumber(otherPoolsEstimation.balance).isGreaterThan(0)) {
const estimatedProfit = new BigNumber(otherPoolsEstimation.balance).multipliedBy(
new BigNumber(provider.maxApy).dividedBy(100),
);

const profitDiff = estimatedProfit.minus(otherPoolsEstimation.estimatedProfit);

if (profitDiff.isGreaterThan(0)) {
return t('staking.estimated_profit_compare', {
amount: formatter.format(profitDiff),
});
}
}

const balance = new BigNumber(tonBalance);

if (balance.isGreaterThanOrEqualTo(10)) {
Expand All @@ -156,7 +193,7 @@ export const Staking: FC<Props> = () => {
});
}
},
[tonBalance],
[otherPoolsEstimation, tonBalance],
);

useEffect(() => {
Expand Down
19 changes: 6 additions & 13 deletions packages/mobile/src/core/StakingPoolDetails/StakingPoolDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { usePoolInfo } from '$hooks/usePoolInfo';
import { useStakingRefreshControl } from '$hooks/useStakingRefreshControl';
import { MainStackRouteNames, openDAppBrowser, openJetton } from '$navigation';
import { MainStackParamList } from '$navigation/MainStack';
import { NextCycle } from '$shared/components';
import { getServerConfig, KNOWN_STAKING_IMPLEMENTATIONS } from '$shared/constants';
import { getStakingPoolByAddress, getStakingProviderById, useStakingStore } from '$store';
import {
Expand Down Expand Up @@ -319,20 +318,14 @@ export const StakingPoolDetails: FC<Props> = (props) => {
</S.BalanceTouchableContainer>
</>
) : null}
{hasAnyBalance ? (
{/* {hasAnyBalance && stakingJetton && isLiquidTF ? (
<>
<Spacer y={16} />
<NextCycle pool={pool} nextReward={nextReward} />
{/* {stakingJetton && isLiquidTF ? (
<>
<Spacer y={24} />
<S.ChartContainer>
<StakingChart stakingJetton={stakingJetton} />
</S.ChartContainer>
</>
) : null} */}
<Spacer y={24} />
<S.ChartContainer>
<StakingChart stakingJetton={stakingJetton} />
</S.ChartContainer>
</>
) : null}
) : null} */}
<Spacer y={8} />
<S.TitleContainer>
<Text variant="h3">{t('staking.details.about_pool')}</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,19 @@ const AmountStepComponent: FC<Props> = (props) => {
isLiquidJetton,
} = useCurrencyToSend(currency, isJetton);

const walletBalance = isLiquidJetton ? price!.totalTon : tonBalance;
const availableTonBalance = useMemo(() => {
if (pool.implementation === PoolImplementationType.LiquidTF && !isWithdrawal) {
const tonAmount = new BigNumber(tonBalance).minus(1.1);

return tonAmount.isGreaterThanOrEqualTo(0)
? tonAmount.decimalPlaces(Decimals[CryptoCurrencies.Ton]).toString()
: '0';
}

return tonBalance;
}, [isWithdrawal, pool.implementation, tonBalance]);

const walletBalance = isLiquidJetton ? price!.totalTon : availableTonBalance;

const minAmount = isWithdrawal ? '0' : Ton.fromNano(pool.min_stake);

Expand Down
16 changes: 15 additions & 1 deletion packages/mobile/src/hooks/usePoolInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import {
PoolImplementationType,
} from '@tonkeeper/core/src/TonAPI';
import { useGetTokenPrice, useTokenPrice } from './useTokenPrice';
import { openInsufficientFundsModal } from '$core/ModalContainer/InsufficientFunds/InsufficientFunds';
import { useCurrencyToSend } from './useCurrencyToSend';
import { Ton } from '$libs/Ton';

export interface PoolDetailsItem {
label: string;
Expand All @@ -34,6 +37,8 @@ export const usePoolInfo = (pool: PoolInfo, poolStakingInfo?: AccountStakingInfo

const wallet = useWallet();

const { balance: tonBalance } = useCurrencyToSend(CryptoCurrencies.Ton);

const jettonBalances = useSelector(jettonsBalancesSelector);

const highestApyPool = useStakingStore((s) => s.highestApyPool, shallow);
Expand Down Expand Up @@ -113,14 +118,23 @@ export const usePoolInfo = (pool: PoolInfo, poolStakingInfo?: AccountStakingInfo

const handleTopUpPress = useCallback(() => {
if (wallet) {
const canDeposit = new BigNumber(tonBalance).isGreaterThanOrEqualTo(2.1);
if (!canDeposit) {
return openInsufficientFundsModal({
totalAmount: Ton.toNano(2.1),
balance: Ton.toNano(tonBalance),
isStakingDeposit: true,
});
}

nav.push(AppStackRouteNames.StakingSend, {
poolAddress: pool.address,
transactionType: StakingTransactionType.DEPOSIT,
});
} else {
openRequireWalletModal();
}
}, [nav, pool.address, wallet]);
}, [nav, pool.address, tonBalance, wallet]);

const handleWithdrawalPress = useCallback(() => {
if (!hasDeposit) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
import styled from '$styled';
import { changeAlphaValue, convertHexToRGBA, ns } from '$utils';
import Animated from 'react-native-reanimated';
import { ns } from '$utils';

export const Container = styled.View`
background: ${({ theme }) => theme.colors.backgroundSecondary};
border-radius: ${ns(16)}px;
padding: ${ns(16)}px;
overflow: hidden;
position: relative;
`;

export const ProgressView = styled(Animated.View)`
position: absolute;
top: 0;
left: 0;
bottom: 0;
background: ${({ theme }) => theme.colors.backgroundTertiary};
`;

export const Row = styled.View`
Expand Down
55 changes: 6 additions & 49 deletions packages/mobile/src/shared/components/NextCycle/NextCycle.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,20 @@
import { useStakingCycle } from '$hooks/useStakingCycle';
import { Text } from '$uikit';
import React, { FC, memo, useCallback } from 'react';
import React, { FC, memo } from 'react';
import * as S from './NextCycle.style';
import { LayoutChangeEvent } from 'react-native';
import { interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
import { t } from '@tonkeeper/shared/i18n';
import { PoolInfo, PoolImplementationType } from '@tonkeeper/core/src/TonAPI';

interface Props {
pool: PoolInfo;
nextReward?: string;
}

const NextCycleComponent: FC<Props> = (props) => {
const {
pool: { cycle_start, cycle_end, implementation },
nextReward,
} = props;

const { formattedDuration, progress, isCooldown } = useStakingCycle(
cycle_start,
cycle_end,
);

const containerWidth = useSharedValue(0);

const handleLayout = useCallback(
(event: LayoutChangeEvent) => {
containerWidth.value = event.nativeEvent.layout.width;
},
[containerWidth],
);

const progressAnimatedStyle = useAnimatedStyle(() => ({
width: interpolate(progress.value, [0, 1], [0, containerWidth.value]),
}));
const { formattedDuration, isCooldown } = useStakingCycle(cycle_start, cycle_end);

if (isCooldown && implementation !== PoolImplementationType.LiquidTF) {
return (
Expand All @@ -51,33 +31,10 @@ const NextCycleComponent: FC<Props> = (props) => {
}

return (
<S.Container onLayout={handleLayout}>
<S.ProgressView style={progressAnimatedStyle} />
<S.Row>
<Text variant="label1">
{nextReward ? `+ ${nextReward} TON` : t('staking.details.next_cycle.title')}
</Text>
<Text
color={nextReward ? 'foregroundPrimary' : 'foregroundSecondary'}
variant={nextReward ? 'label1' : 'body1'}
>
{t('staking.details.next_cycle.in')}{' '}
<Text
color={nextReward ? 'foregroundPrimary' : 'foregroundSecondary'}
variant={nextReward ? 'label1' : 'body1'}
style={{ fontVariant: ['tabular-nums'] }}
>
{formattedDuration}
</Text>
</Text>
</S.Row>
{!nextReward ? (
<Text variant="body2" color="foregroundSecondary">
{implementation === PoolImplementationType.LiquidTF
? t('staking.details.next_cycle.desc_liquid')
: t('staking.details.next_cycle.desc')}
</Text>
) : null}
<S.Container>
<Text color="foregroundSecondary" variant="body2">
{t('staking.details.next_cycle.message', { value: formattedDuration })}
</Text>
</S.Container>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { MainStackRouteNames } from '$navigation';
import { t } from '@tonkeeper/shared/i18n';
import { StakedTonIcon, Text } from '$uikit';
import { stakingFormatter } from '@tonkeeper/shared/formatter';
import { AccountStakingInfo, PoolInfo } from '@tonkeeper/core/src/TonAPI';
import {
AccountStakingInfo,
PoolImplementationType,
PoolInfo,
} from '@tonkeeper/core/src/TonAPI';

interface Props {
pool: PoolInfo;
Expand Down Expand Up @@ -50,6 +54,13 @@ const StakingWidgetStatusComponent: FC<Props> = (props) => {
}

if (hasPendingWithdraw) {
if (pool.implementation === PoolImplementationType.LiquidTF) {
return t('staking.message.pendingWithdrawLiquid', {
amount: stakingFormatter.format(pendingWithdraw.amount),
count: pendingWithdraw.totalTon,
});
}

return (
<>
{t('staking.message.pendingWithdraw', {
Expand Down
Loading

0 comments on commit 807a055

Please sign in to comment.