Staking Components
Pre-built React components for YANI token staking with pools, positions, and calculators.
Composants internes au dashboard YaniPay
@yanipay/ui publié sur npm. Les exemples ci-dessous montrent les patterns réels utilisés dans la codebase, avec les imports corrects depuis les chemins internes du projet.Overview
The staking component library provides everything you need to build a complete staking interface. Components handle pool selection, position management, and reward tracking.
Multiple Lock Periods
Flexible, 90-day, and 365-day options
Real-time Rewards
Live reward accrual and APY display
YaniChain Staking
Les composants de staking sont connectés nativement à YaniChain, la blockchain propriétaire de YaniPay. Cela offre des avantages uniques pour vos utilisateurs.
Optimisation Y.A.N.I.
StakingCard Component
A complete staking interface in a single card component:
1 // NOTE: Pas de StakingCard standalone dans @yanipay/ui (package inexistant). 2 // Le staking est géré dans le dashboard via les hooks web3 + l'API REST. 3 // Chemin réel : app/(dashboard)/dashboard/defi/staking/PageContent.tsx 4 5 'use client'; 6 7 import { 8 useAccount, 9 useStakingStats, 10 useUserStakes, 11 useTierConfig, 12 useStake, 13 useUnstake, 14 useClaimRewards, 15 useCompoundRewards, 16 useYaniTokenBalance, 17 useYaniApprove, 18 StakingTier, 19 STAKING_TIER_NAMES, 20 } from '@/lib/web3'; 21 import { useChainId } from 'wagmi'; 22 import { defiApi } from '@platform/api/clients/web'; 23 import type { StakingPoolDTO } from '@platform/api/contracts/defi'; 24 import { parseEther } from 'viem'; 25 import { toast } from 'sonner'; 26 27 export function useStakingPage() { 28 const { address, isConnected } = useAccount(); 29 const chainId = useChainId(); 30 31 // Données on-chain via wagmi 32 const { data: stakingStats } = useStakingStats(); 33 const { data: userStakes } = useUserStakes(address); 34 const { stake, isPending: stakePending } = useStake(); 35 const { unstake } = useUnstake(); 36 const { claimRewards } = useClaimRewards(); 37 38 // Pools via API REST 39 async function loadPools(): Promise<StakingPoolDTO[]> { 40 // GET /api/defi/staking/pools 41 return defiApi.getStakingPools(); 42 } 43 44 // Staker des tokens (tier 0=flexible, 1=bronze, 2=argent, 3=or, 4=platine) 45 async function handleStake(amount: string, tier: number) { 46 const amountWei = parseEther(amount); 47 await stake(amountWei, tier as StakingTier); 48 toast.success(`${amount} YANI stakés avec succès`); 49 } 50 51 // Réclamer les récompenses 52 async function handleClaim(positionIndex: number) { 53 await claimRewards(positionIndex); 54 toast.success('Récompenses réclamées'); 55 } 56 57 return { stakingStats, userStakes, stakePending, handleStake, handleClaim }; 58 }
StakingPoolList Component
Display available staking pools with APY and TVL information:
1 // NOTE: Pas de StakingPoolList standalone. Endpoint réel : GET /api/defi/staking/pools 2 // Les tiers du dashboard correspondent à StakingTier enum de lib/web3. 3 4 import { apiGet } from '@/lib/api/client'; 5 import { STAKING_TIER_NAMES } from '@/lib/web3'; 6 import type { StakingPoolDTO } from '@platform/api/contracts/defi'; 7 8 // Charger les pools disponibles 9 async function loadStakingPools(): Promise<StakingPoolDTO[]> { 10 // GET /api/defi/staking/pools?featured=true 11 return apiGet<StakingPoolDTO[]>('/api/defi/staking/pools?featured=true'); 12 } 13 14 // Rendu d'une pool card (pattern dashboard) 15 function PoolCard({ 16 pool, 17 isSelected, 18 onSelect, 19 }: { 20 pool: StakingPoolDTO; 21 isSelected: boolean; 22 onSelect: () => void; 23 }) { 24 return ( 25 <div 26 onClick={onSelect} 27 className={`p-6 rounded-xl border-2 cursor-pointer transition-all ${ 28 isSelected 29 ? 'border-cyan-500 bg-cyan-500/10' 30 : 'border-gray-700 hover:border-gray-600' 31 }`} 32 > 33 <div className="flex justify-between items-start mb-4"> 34 <div> 35 <h3 className="text-lg font-bold">{pool.name}</h3> 36 <p className="text-sm text-gray-400"> 37 {pool.lockPeriodDays > 0 38 ? `${pool.lockPeriodDays} jours de lock` 39 : 'Flexible (sans lock)'} 40 </p> 41 </div> 42 <div className="text-right"> 43 <p className="text-2xl font-bold text-cyan-400">{pool.apy}%</p> 44 <p className="text-xs text-gray-500">APY</p> 45 </div> 46 </div> 47 48 <div className="flex justify-between text-sm"> 49 <span className="text-gray-500">TVL</span> 50 <span>${(pool.tvlUsd / 1_000_000).toFixed(1)}M</span> 51 </div> 52 </div> 53 ); 54 }
StakingPosition Component
Display and manage individual staking positions:
1 // NOTE: Les positions de staking sont gérées via les hooks web3 on-chain. 2 // Endpoint REST : GET /api/defi/staking/positions 3 // Hook on-chain : useUserStakes(address) depuis lib/web3 4 5 import { useUserStakes, useClaimRewards, useUnstake } from '@/lib/web3'; 6 import { apiGet } from '@/lib/api/client'; 7 import { formatEther } from 'viem'; 8 import { toast } from 'sonner'; 9 10 // Positions on-chain (source de vérité) 11 export function useMyStakingPositions(address?: string) { 12 const { data: positions, isLoading, refetch } = useUserStakes(address); 13 const { claimRewards, isPending: claimPending } = useClaimRewards(); 14 const { unstake, isPending: unstakePending } = useUnstake(); 15 16 async function handleClaim(positionIndex: number) { 17 await claimRewards(positionIndex); 18 await refetch(); 19 toast.success('Récompenses réclamées avec succès'); 20 } 21 22 async function handleUnstake(positionIndex: number, amount: bigint) { 23 await unstake(positionIndex, amount); 24 await refetch(); 25 toast.success('Unstake effectué avec succès'); 26 } 27 28 return { 29 positions: positions ?? [], 30 isLoading, 31 claimPending, 32 unstakePending, 33 handleClaim, 34 handleUnstake, 35 }; 36 } 37 38 // Rendu d'une position 39 function PositionRow({ position, index, onClaim, onUnstake }: { 40 position: { amount: bigint; pendingRewards: bigint; isLocked: boolean }; 41 index: number; 42 onClaim: (i: number) => void; 43 onUnstake: (i: number, amount: bigint) => void; 44 }) { 45 const amount = parseFloat(formatEther(position.amount)).toFixed(2); 46 const rewards = parseFloat(formatEther(position.pendingRewards)).toFixed(4); 47 48 return ( 49 <div className="flex items-center justify-between p-4 rounded-xl border border-gray-700"> 50 <div> 51 <p className="font-semibold">{amount} YANI stakés</p> 52 <p className="text-sm text-cyan-400">{rewards} YANI en récompenses</p> 53 </div> 54 <div className="flex gap-2"> 55 <button 56 onClick={() => onClaim(index)} 57 disabled={position.pendingRewards === 0n} 58 className="px-3 py-1.5 bg-emerald-600 disabled:bg-gray-600 text-white text-sm rounded-lg" 59 > 60 Réclamer 61 </button> 62 {!position.isLocked && ( 63 <button 64 onClick={() => onUnstake(index, position.amount)} 65 className="px-3 py-1.5 bg-gray-700 text-white text-sm rounded-lg" 66 > 67 Unstake 68 </button> 69 )} 70 </div> 71 </div> 72 ); 73 }
StakingCalculator Component
Interactive calculator for estimating staking rewards:
1 // NOTE: Le calculateur appelle POST /api/defi/staking/calculate 2 // Pas de StakingCalculator dans @yanipay/ui (package inexistant). 3 4 import { useState } from 'react'; 5 import { apiGet } from '@/lib/api/client'; 6 7 interface StakingCalculation { 8 principal: number; 9 rewards: number; 10 totalValue: number; 11 apy: number; 12 duration: number; // jours 13 } 14 15 // Endpoint réel : GET /api/defi/staking/calculate?amount=1000&poolId=locked-90&days=90 16 async function calculateRewards( 17 amount: number, 18 poolId: string, 19 days: number 20 ): Promise<StakingCalculation> { 21 return apiGet<StakingCalculation>( 22 `/api/defi/staking/calculate?amount=${amount}&poolId=${poolId}&days=${days}` 23 ); 24 } 25 26 export function StakingCalculatorExample() { 27 const [amount, setAmount] = useState('1000'); 28 const [poolId, setPoolId] = useState('locked-90'); 29 const [result, setResult] = useState<StakingCalculation | null>(null); 30 31 const calculate = async () => { 32 const data = await calculateRewards(parseFloat(amount), poolId, 90); 33 setResult(data); 34 }; 35 36 return ( 37 <div className="p-6 rounded-xl border border-gray-700 space-y-4"> 38 <input 39 type="number" 40 value={amount} 41 onChange={(e) => setAmount(e.target.value)} 42 placeholder="Montant YANI" 43 className="w-full px-3 py-2 bg-gray-800 rounded-lg" 44 /> 45 <select 46 value={poolId} 47 onChange={(e) => setPoolId(e.target.value)} 48 className="w-full px-3 py-2 bg-gray-800 rounded-lg" 49 > 50 <option value="flexible">Flexible (sans lock)</option> 51 <option value="locked-90">90 jours</option> 52 <option value="locked-365">365 jours</option> 53 </select> 54 <button 55 onClick={calculate} 56 className="w-full py-2 bg-cyan-600 text-white rounded-lg" 57 > 58 Calculer 59 </button> 60 {result && ( 61 <div className="mt-4 p-4 bg-gray-800 rounded-lg"> 62 <p>Récompenses estimées : <strong>{result.rewards.toFixed(2)} YANI</strong></p> 63 <p>APY : <strong>{result.apy}%</strong></p> 64 </div> 65 )} 66 </div> 67 ); 68 }
Props Reference
StakingCard Props
| Prop | Type | Description |
|---|---|---|
token | string | Token symbol to stake |
pools | string[] | Pool IDs to show |
showVotingPower | boolean | Show voting power impact |
onStake | (poolId, amount) => void | Stake callback |
onClaim | (positionId) => void | Claim callback |
StakingCalculator Props
| Prop | Type | Description |
|---|---|---|
defaultAmount | string | Initial amount value |
mode | 'single' | 'compare' | Calculator mode |
showCompoundingOptions | boolean | Show compound settings |
compoundFrequency | 'daily' | 'weekly' | 'monthly' | Auto-compound frequency |
Related Resources
Derniere mise a jour : 2026-03-31