Skip to content

Commit

Permalink
feat(unstaking): Instant unstake forecasting
Browse files Browse the repository at this point in the history
  • Loading branch information
EvgeniiVoznyuk committed Apr 24, 2024
1 parent 621a147 commit afe26fb
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/suite-data/files/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1740,6 +1740,7 @@
"TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "Unstaked and ready to claim",
"TR_STAKE_UNSTAKE_TO_CLAIM": "Unstake to claim",
"TR_STAKE_UNSTAKING": "Unstaking",
"TR_STAKE_UNSTAKING_APPROXIMATE": "Approximate ETH available instantly",
"TR_STAKE_UNSTAKING_PERIOD": "Unstaking period",
"TR_STAKE_UNSTAKING_TAKES": "Unstaking usually takes about 3 days. Once completed, you can trade or send it.",
"TR_STAKE_WAITING_TO_BE_ADDED": "Your {symbol} is waiting to be added {br} to the staking pool.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useState } from 'react';
import styled from 'styled-components';
import { FiatValue, FormattedCryptoAmount, Translation } from 'src/components/suite';
import { Paragraph, Radio } from '@trezor/components';
Expand Down Expand Up @@ -61,16 +60,14 @@ const InputsWrapper = styled.div<{ $isShown: boolean }>`
display: ${({ $isShown }) => ($isShown ? 'block' : 'none')};
`;

type UnstakeOptions = 'all' | 'rewards' | 'other';

interface OptionsProps {
symbol: NetworkSymbol;
}

export const Options = ({ symbol }: OptionsProps) => {
const selectedAccount = useSelector(selectSelectedAccount);
const { unstakeOption, setUnstakeOption } = useUnstakeEthFormContext();

const [unstakeOption, setUnstakeOption] = useState<UnstakeOptions>('all');
const isRewardsSelected = unstakeOption === 'rewards';
const isAllSelected = unstakeOption === 'all';
const isOtherAmountSelected = unstakeOption === 'other';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from 'styled-components';
import { Button, Divider, Paragraph, Warning } from '@trezor/components';
import { Button, Divider, Icon, Paragraph, Tooltip, Warning } from '@trezor/components';
import { spacingsPx } from '@trezor/theme';
import { Translation } from 'src/components/suite';
import { FormattedCryptoAmount, Translation } from 'src/components/suite';
import { useDevice, useSelector } from 'src/hooks/suite';
import { useUnstakeEthFormContext } from 'src/hooks/wallet/useUnstakeEthForm';
import { selectSelectedAccount } from 'src/reducers/wallet/selectedAccountReducer';
Expand Down Expand Up @@ -44,6 +44,19 @@ const UpToDaysWrapper = styled.div`
border-top: 1px solid ${({ theme }) => theme.borderElevation2};
`;

const ApproximateEthWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: ${spacingsPx.xxs} 0 ${spacingsPx.md};
`;

const ApproximateEthTitleWrapper = styled.div`
display: flex;
gap: ${spacingsPx.xxs};
align-items: center;
`;

export const UnstakeEthForm = () => {
const { device, isLocked } = useDevice();
const selectedAccount = useSelector(selectSelectedAccount);
Expand All @@ -55,6 +68,8 @@ export const UnstakeEthForm = () => {
handleSubmit,
watch,
signTx,
approximatedEthAmount,
unstakeOption,
} = useUnstakeEthFormContext();

const { symbol } = account;
Expand All @@ -66,6 +81,7 @@ export const UnstakeEthForm = () => {
const hasValues = Boolean(watch(FIAT_INPUT) || watch(CRYPTO_INPUT));
// used instead of formState.isValid, which is sometimes returning false even if there are no errors
const formIsValid = Object.keys(errors).length === 0;
const isUnstakeOptionOther = unstakeOption === 'other';

const { canClaim = false, claimableAmount = '0' } =
getAccountEverstakeStakingPool(selectedAccount) ?? {};
Expand Down Expand Up @@ -115,6 +131,34 @@ export const UnstakeEthForm = () => {
/>
</UpToDaysWrapper>

{isUnstakeOptionOther && (
<ApproximateEthWrapper>
<ApproximateEthTitleWrapper>
<GreyP>
<Translation id="TR_STAKE_UNSTAKING_APPROXIMATE" />
</GreyP>

<Tooltip
maxWidth={328}
content={
<Translation id="TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION" />
}
>
<Icon icon="INFO" size={14} />
</Tooltip>
</ApproximateEthTitleWrapper>

{approximatedEthAmount && (
<FormattedCryptoAmount
disableHiddenPlaceholder
value={approximatedEthAmount}
symbol={symbol}
isBalance
/>
)}
</ApproximateEthWrapper>
)}

<Button
type="submit"
isFullWidth
Expand Down
31 changes: 30 additions & 1 deletion packages/suite/src/hooks/wallet/useUnstakeEthForm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, useCallback, useContext, useEffect, useMemo } from 'react';
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useForm, useWatch } from 'react-hook-form';

import {
Expand Down Expand Up @@ -36,8 +36,13 @@ import { Ethereum } from '@everstake/wallet-sdk';
import { useFees } from './form/useFees';
import { getAccountAutocompoundBalance } from 'src/utils/wallet/stakingUtils';

type UnstakeOptions = 'all' | 'rewards' | 'other';

type UnstakeContextValues = UnstakeContextValuesBase & {
amountLimits: AmountLimitsString;
approximatedEthAmount?: string | null;
unstakeOption: UnstakeOptions;
setUnstakeOption: (option: UnstakeOptions) => void;
};

export const UnstakeEthFormContext = createContext<UnstakeContextValues | null>(null);
Expand All @@ -47,6 +52,8 @@ export const useUnstakeEthForm = ({
selectedAccount,
}: UseStakeFormsProps): UnstakeContextValues => {
const dispatch = useDispatch();
const [approximatedEthAmount, setApproximatedEthAmount] = useState<string | null>(null);
const [unstakeOption, setUnstakeOption] = useState<UnstakeOptions>('all');

const { account, network } = selectedAccount;
const { symbol } = account;
Expand Down Expand Up @@ -103,6 +110,25 @@ export const useUnstakeEthForm = ({

const values = useWatch<UnstakeFormState>({ control });

useEffect(() => {
(async () => {
const { cryptoInput } = values;

if (!cryptoInput || Object.keys(formState.errors).length) {
setApproximatedEthAmount(null);

return;
}

const approximatedEthAmount = await Ethereum.simulateUnstake(
account.descriptor,
cryptoInput,
);

setApproximatedEthAmount(approximatedEthAmount.toString());
})();
}, [account, formState.errors, values]);

useEffect(() => {
if (!isChanged(defaultValues, values)) {
removeDraft(account.key);
Expand Down Expand Up @@ -255,6 +281,9 @@ export const useUnstakeEthForm = ({
currentRate,
feeInfo,
changeFeeLevel,
approximatedEthAmount,
unstakeOption,
setUnstakeOption,
};
};

Expand Down
9 changes: 9 additions & 0 deletions packages/suite/src/support/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8717,6 +8717,15 @@ export default defineMessages({
id: 'TR_STAKE_UNSTAKING_PERIOD',
defaultMessage: 'Unstaking period',
},
TR_STAKE_UNSTAKING_APPROXIMATE: {
id: 'TR_STAKE_UNSTAKING_APPROXIMATE',
defaultMessage: 'Approximate ETH available instantly',
},
TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION: {
id: 'TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION',
defaultMessage:
'Liquidity of the staking pool can allow for instant unstake of some funds. Remaining funds will follow the unstaking period',
},
TR_UP_TO_DAYS: {
id: 'TR_UP_TO_DAYS',
defaultMessage: 'up to {days} days',
Expand Down

0 comments on commit afe26fb

Please sign in to comment.