145 lines
4.7 KiB
TypeScript
145 lines
4.7 KiB
TypeScript
import { useState } from 'react'
|
||
import { Notification } from '@shared/ui'
|
||
import { truncateDecimals } from '@shared/lib/utils/truncateDecimals'
|
||
import { ChainToggle } from './ChainToggle'
|
||
import { StakeCard } from './StakeCard'
|
||
import { PositionsPanel } from './PositionsPanel'
|
||
import { UnstakeModal } from './UnstakeModal'
|
||
import {
|
||
useStake,
|
||
useUnstake,
|
||
useClaim,
|
||
getStakingErrorMessage,
|
||
type StakeChain,
|
||
type SolPosition,
|
||
} from '@features/staking'
|
||
import { CHAIN_META } from '../model/meta'
|
||
import styles from './StakingWidget.module.css'
|
||
|
||
type ModalState = { kind: 'eth'; amount: string } | { kind: 'sol'; position: SolPosition } | null
|
||
|
||
type Toast = { status: 'success' | 'error' | 'info'; message: string } | null
|
||
|
||
export function StakingWidget() {
|
||
const [chain, setChain] = useState<StakeChain>('ETH')
|
||
const [amount, setAmount] = useState('')
|
||
const [modal, setModal] = useState<ModalState>(null)
|
||
const [toast, setToast] = useState<Toast>(null)
|
||
|
||
const stakeM = useStake()
|
||
const unstakeM = useUnstake()
|
||
const claimM = useClaim()
|
||
|
||
function handleChain(next: StakeChain) {
|
||
setChain(next)
|
||
setAmount('')
|
||
}
|
||
|
||
// stakeAmountHuman — это сумма стейка A (= введённое − газ), её и отправляем на бэкенд.
|
||
function handleStake(stakeAmountHuman: string) {
|
||
stakeM.mutate(
|
||
{ chain, amountHuman: stakeAmountHuman },
|
||
{
|
||
onSuccess: () => {
|
||
setToast({
|
||
status: 'success',
|
||
message: `Стейкинг ${truncateDecimals(stakeAmountHuman, 6)} ${CHAIN_META[chain].symbol} выполнен`,
|
||
})
|
||
setAmount('')
|
||
},
|
||
onError: (e) => setToast({ status: 'error', message: getStakingErrorMessage(e) }),
|
||
}
|
||
)
|
||
}
|
||
|
||
function handleConfirm(mode?: 'swap' | 'queue') {
|
||
if (modal?.kind === 'eth') {
|
||
const selected = mode ?? 'swap'
|
||
unstakeM.mutate(
|
||
{ chain: 'ETH', amountHuman: modal.amount, mode: selected },
|
||
{
|
||
onSuccess: () => {
|
||
setModal(null)
|
||
setToast({
|
||
status: 'success',
|
||
message:
|
||
selected === 'queue' ? 'Заявка на вывод добавлена в очередь Lido' : 'Вывод stETH → ETH выполнен',
|
||
})
|
||
},
|
||
onError: (e) => setToast({ status: 'error', message: getStakingErrorMessage(e) }),
|
||
}
|
||
)
|
||
} else if (modal?.kind === 'sol') {
|
||
unstakeM.mutate(
|
||
{ chain: 'SOL', stakeAccount: modal.position.stakeAccount },
|
||
{
|
||
onSuccess: (res) => {
|
||
setModal(null)
|
||
const withdrawn = 'action' in res && res.action === 'withdraw'
|
||
setToast({
|
||
status: 'success',
|
||
message: withdrawn ? 'Средства выведены на кошелёк' : 'Деактивация запущена — вывод через 1–2 эпохи',
|
||
})
|
||
},
|
||
onError: (e) => setToast({ status: 'error', message: getStakingErrorMessage(e) }),
|
||
}
|
||
)
|
||
}
|
||
}
|
||
|
||
function handleClaim(requestId: string) {
|
||
claimM.mutate(requestId, {
|
||
onSuccess: () => setToast({ status: 'success', message: 'ETH забран на кошелёк' }),
|
||
onError: (e) => setToast({ status: 'error', message: getStakingErrorMessage(e) }),
|
||
})
|
||
}
|
||
|
||
return (
|
||
<div className={styles.widget}>
|
||
<ChainToggle value={chain} onChange={handleChain} />
|
||
|
||
<div className={styles.grid}>
|
||
<StakeCard
|
||
chain={chain}
|
||
amount={amount}
|
||
onAmountChange={setAmount}
|
||
onStake={handleStake}
|
||
isStaking={stakeM.isPending}
|
||
/>
|
||
<PositionsPanel
|
||
chain={chain}
|
||
onUnstakeEth={(amountHuman) => setModal({ kind: 'eth', amount: amountHuman })}
|
||
onUnstakeSol={(position) => setModal({ kind: 'sol', position })}
|
||
onClaim={handleClaim}
|
||
unstakePending={unstakeM.isPending}
|
||
claimPending={claimM.isPending}
|
||
/>
|
||
</div>
|
||
|
||
{modal?.kind === 'eth' && (
|
||
<UnstakeModal
|
||
chain="ETH"
|
||
symbol="ETH"
|
||
amount={modal.amount}
|
||
pending={unstakeM.isPending}
|
||
onConfirm={handleConfirm}
|
||
onClose={() => setModal(null)}
|
||
/>
|
||
)}
|
||
{modal?.kind === 'sol' && (
|
||
<UnstakeModal
|
||
chain="SOL"
|
||
symbol="SOL"
|
||
amount={modal.position.lamportsHuman}
|
||
solState={modal.position.state}
|
||
pending={unstakeM.isPending}
|
||
onConfirm={handleConfirm}
|
||
onClose={() => setModal(null)}
|
||
/>
|
||
)}
|
||
|
||
{toast && <Notification status={toast.status} message={toast.message} onClose={() => setToast(null)} duration={5000} />}
|
||
</div>
|
||
)
|
||
}
|