import { useState } from 'react' import { useTranslation } from 'react-i18next' import { HugeDecimal } from '@dao-dao/math' import { LoadingData } from '@dao-dao/types' import { StakingModalProps, StakingMode, } from '@dao-dao/types/components/StakingModal' import { Duration } from '@dao-dao/types/contracts/common' import { convertDurationToHumanReadableString } from '@dao-dao/utils' import { Button } from '../buttons/Button' import { InputLabel, NumericInput, PercentButton, SegmentedControls, TokenInput, } from '../inputs' import { Modal } from '../modals/Modal' import { Tooltip } from '../tooltip/Tooltip' import { ValidatorPicker } from '../ValidatorPicker' import { TokenAmountDisplay } from './TokenAmountDisplay' export const StakingModal = ({ initialMode, amount, setAmount, onClose, claimableTokens, loadingStakableTokens, loadingUnstakableTokens, unstakingDuration, token, proposalDeposit, loading, error, onAction, actionPrefix, validatorPicker, visible = true, enableRestaking, tokenPicker, }: StakingModalProps) => { const { t } = useTranslation() const [mode, setMode] = useState(initialMode) const [validator, setValidator] = useState() const [fromValidator, setFromValidator] = useState() // If choosing a validator, unstakable amount depends on chosen validator. if (validatorPicker) { // If restaking, fromValidator is source of funds. const targetValidator = mode === StakingMode.Restake ? fromValidator : validator loadingUnstakableTokens = { loading: false, data: validatorPicker.stakes?.find( (stake) => stake.validator.address === targetValidator )?.amount ?? HugeDecimal.zero, } } // If not choosing a validator and no unstakable amount passed, assume 0. else if (!loadingUnstakableTokens) { loadingUnstakableTokens = { loading: false, data: HugeDecimal.zero, } } const maxTx = mode === StakingMode.Stake ? loadingStakableTokens.loading ? undefined : loadingStakableTokens.data : // Unstaking or restaking. !loadingUnstakableTokens || loadingUnstakableTokens.loading ? undefined : loadingUnstakableTokens.data const invalidAmount = (): string | undefined => { if (mode === StakingMode.Claim) { return claimableTokens.isPositive() ? undefined : t('error.cannotTxZeroTokens') } if (!amount.isPositive()) { return t('error.cannotTxZeroTokens') } if (maxTx === undefined) { return t('error.loadingData') } if (amount.gt(maxTx)) { return t('error.cannotStakeMoreThanYouHave') } } return ( } header={{ title: mode === StakingMode.Claim ? t('title.claimTokens') : t('title.manageStaking'), }} headerContent={ mode !== StakingMode.Claim || validatorPicker ? (
{mode !== StakingMode.Claim && ( )} {validatorPicker && ( <> {/* Show from validator. */} {mode === StakingMode.Restake && (
setFromValidator(address)} selectedAddress={fromValidator} token={token} validators={ // Can only restake from validators with stakes. validatorPicker.validators.filter((v) => validatorPicker.stakes?.some( (s) => s.validator.address === v.address && s.amount.isPositive() ) ) } />
)}
{mode === StakingMode.Restake && ( )} setValidator(address)} selectedAddress={validator} token={token} validators={ // If restaking, show all validators for destination. mode === StakingMode.Stake || mode === StakingMode.Restake ? validatorPicker.validators : validatorPicker.validators.filter((v) => validatorPicker.stakes?.some( (s) => s.validator.address === v.address && s.amount.isPositive() ) ) } />
)} {tokenPicker && }
) : undefined } onClose={onClose} visible={visible} > {mode === StakingMode.Claim ? ( ) : ( )}
) } interface StakeUnstakeModesBodyProps { amount: HugeDecimal mode: StakingMode loadingMax: LoadingData setAmount: (newAmount: HugeDecimal) => void tokenSymbol: string tokenDecimals: number unstakingDuration: Duration | null proposalDeposit?: HugeDecimal } const StakeUnstakeModesBody = ({ amount, setAmount, mode, loadingMax, tokenSymbol, tokenDecimals, unstakingDuration, proposalDeposit, }: StakeUnstakeModesBodyProps) => { const { t } = useTranslation() return ( <>

{t('title.chooseTokenAmount')}

setAmount(HugeDecimal.fromHumanReadable(value, tokenDecimals)) } step={HugeDecimal.one.toHumanReadableNumber(tokenDecimals)} textClassName="font-mono leading-5 symbol-small-body-text" unit={'$' + tokenSymbol} value={amount.toHumanReadableString(tokenDecimals)} /> {!loadingMax.loading && loadingMax.data.lt(amount) && ( {t('error.cannotStakeMoreThanYouHave')} )}
{[10, 25, 50, 75, 100].map((percent) => ( ))}
{mode === StakingMode.Stake && !!proposalDeposit && !loadingMax.loading && loadingMax.data.gt(proposalDeposit) && ( )}
{(mode === StakingMode.Stake || mode === StakingMode.Unstake || mode === StakingMode.Restake) && unstakingDuration && ('height' in unstakingDuration ? unstakingDuration.height : unstakingDuration.time) > 0 && (

{t('title.unstakingPeriod') + `: ${convertDurationToHumanReadableString( t, unstakingDuration )}`}

{t('info.unstakingMechanics', { humanReadableTime: convertDurationToHumanReadableString( t, unstakingDuration ), })}

)} ) } interface ClaimModeBodyProps { amount: HugeDecimal tokenDecimals: number tokenSymbol: string } const ClaimModeBody = ({ amount, tokenSymbol, tokenDecimals, }: ClaimModeBodyProps) => { const { t } = useTranslation() return (

{t('info.claimToReceiveUnstaked')}

) }