Examples
Ready-to-use code examples and starter templates for building with YaniPay.
Starter Templates
Clone these templates to quickly start building your DeFi application:
Token Swap
A complete token swap implementation with slippage protection:
examples/TokenSwap.tsxtsx
1 'use client'; 2 3 import { useState } from 'react'; 4 import { apiPost } from '@/lib/api/client'; 5 import { ArrowDown, Loader2 } from 'lucide-react'; 6 7 interface SwapQuote { 8 amountOut: string; 9 priceImpact: string; 10 minimumReceived: string; 11 } 12 13 export function TokenSwap() { 14 const [tokenIn, setTokenIn] = useState('YANI'); 15 const [tokenOut, setTokenOut] = useState('USDC'); 16 const [amount, setAmount] = useState(''); 17 const [quote, setQuote] = useState<SwapQuote | null>(null); 18 const [isLoading, setIsLoading] = useState(false); 19 const [error, setError] = useState<{ message: string } | null>(null); 20 21 const handleSwap = async () => { 22 setIsLoading(true); 23 setError(null); 24 try { 25 const result = await apiPost<{ success: boolean; transactionHash: string }>( 26 '/api/blockchain/defi/swap', 27 { tokenIn, tokenOut, amount, slippage: '0.5' } 28 ); 29 if (result.success) { 30 alert(`Swap complete! TX: ${result.transactionHash}`); 31 } 32 } catch (err) { 33 setError({ message: err instanceof Error ? err.message : 'Erreur inconnue' }); 34 } finally { 35 setIsLoading(false); 36 } 37 }; 38 39 return ( 40 <div className="max-w-md mx-auto p-6 bg-gray-900 rounded-2xl"> 41 <h2 className="text-xl font-bold text-white mb-6">Swap Tokens</h2> 42 43 {/* Input Token */} 44 <div className="p-4 bg-gray-800 rounded-xl mb-2"> 45 <div className="flex justify-between text-sm text-gray-400 mb-2"> 46 <span>You pay</span> 47 <span>Balance: 1,000.00</span> 48 </div> 49 <div className="flex gap-4"> 50 <input 51 type="number" 52 value={amount} 53 onChange={(e) => setAmount(e.target.value)} 54 placeholder="0.00" 55 className="flex-1 bg-transparent text-2xl text-white outline-none" 56 /> 57 <select 58 value={tokenIn} 59 onChange={(e) => setTokenIn(e.target.value)} 60 className="px-3 py-2 bg-gray-700 rounded-lg text-white" 61 > 62 <option value="YANI">YANI</option> 63 <option value="USDC">USDC</option> 64 <option value="ETH">ETH</option> 65 </select> 66 </div> 67 </div> 68 69 {/* Swap Direction */} 70 <div className="flex justify-center -my-3 relative z-10"> 71 <button 72 onClick={() => { 73 setTokenIn(tokenOut); 74 setTokenOut(tokenIn); 75 }} 76 className="p-3 bg-gray-800 rounded-xl border-4 border-gray-900" 77 > 78 <ArrowDown className="w-5 h-5 text-white" /> 79 </button> 80 </div> 81 82 {/* Output Token */} 83 <div className="p-4 bg-gray-800 rounded-xl mt-2"> 84 <div className="flex justify-between text-sm text-gray-400 mb-2"> 85 <span>You receive</span> 86 </div> 87 <div className="flex gap-4"> 88 <div className="flex-1 text-2xl text-white"> 89 {isLoading ? ( 90 <Loader2 className="w-6 h-6 animate-spin" /> 91 ) : ( 92 quote?.amountOut || '0.00' 93 )} 94 </div> 95 <select 96 value={tokenOut} 97 onChange={(e) => setTokenOut(e.target.value)} 98 className="px-3 py-2 bg-gray-700 rounded-lg text-white" 99 > 100 <option value="USDC">USDC</option> 101 <option value="YANI">YANI</option> 102 <option value="ETH">ETH</option> 103 </select> 104 </div> 105 </div> 106 107 {/* Quote Details */} 108 {quote && ( 109 <div className="mt-4 p-3 bg-gray-800 rounded-lg text-sm text-gray-400"> 110 <div className="flex justify-between"> 111 <span>Price Impact</span> 112 <span className={parseFloat(quote.priceImpact) > 3 ? 'text-red-400' : ''}> 113 {quote.priceImpact}% 114 </span> 115 </div> 116 <div className="flex justify-between mt-1"> 117 <span>Min. Received</span> 118 <span>{quote.minimumReceived} {tokenOut}</span> 119 </div> 120 </div> 121 )} 122 123 {/* Error Display */} 124 {error && ( 125 <div className="mt-4 p-3 bg-red-900/50 border border-red-500 rounded-lg text-red-400 text-sm"> 126 {error.message} 127 </div> 128 )} 129 130 {/* Swap Button */} 131 <button 132 onClick={handleSwap} 133 disabled={!amount || isLoading} 134 className="w-full mt-6 py-4 bg-purple-600 hover:bg-purple-700 disabled:bg-gray-700 text-white font-semibold rounded-xl transition-colors" 135 > 136 {!isConnected ? 'Connect Wallet' : isLoading ? 'Swapping...' : 'Swap'} 137 </button> 138 </div> 139 ); 140 }
Staking Dashboard
A staking interface with pool selection and position management:
examples/StakingDashboard.tsxtsx
1 'use client'; 2 3 import { useState, useEffect } from 'react'; 4 import { apiGet, apiPost } from '@/lib/api/client'; 5 import { Coins, Lock, TrendingUp } from 'lucide-react'; 6 7 interface StakingPool { id: string; name: string; apy: number; lockPeriod: number; tvl: string; } 8 interface StakingPosition { id: string; amount: string; rewards: string; } 9 10 export function StakingDashboard() { 11 const [pools, setPools] = useState<StakingPool[]>([]); 12 const [positions, setPositions] = useState<StakingPosition[]>([]); 13 14 useEffect(() => { 15 apiGet<{ pools: StakingPool[] }>('/api/blockchain/defi/staking/pools') 16 .then(data => setPools(data.pools)) 17 .catch(console.error); 18 apiGet<{ positions: StakingPosition[] }>('/api/blockchain/defi/staking/positions') 19 .then(data => setPositions(data.positions)) 20 .catch(console.error); 21 }, []); 22 23 const claim = (id: string) => 24 apiPost('/api/blockchain/defi/staking/claim', { positionId: id }); 25 const unstake = (id: string, amount: string) => 26 apiPost('/api/blockchain/defi/staking/unstake', { positionId: id, amount }); 27 28 const totalStaked = positions.reduce( 29 (sum, p) => sum + parseFloat(p.amount), 30 0 31 ); 32 const totalRewards = positions.reduce( 33 (sum, p) => sum + parseFloat(p.rewards), 34 0 35 ); 36 37 return ( 38 <div className="max-w-4xl mx-auto p-6"> 39 {/* Stats Overview */} 40 <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8"> 41 <div className="p-6 bg-gray-900 rounded-xl"> 42 <div className="flex items-center gap-3 mb-2"> 43 <Coins className="w-5 h-5 text-cyan-400" /> 44 <span className="text-gray-400">Total Staked</span> 45 </div> 46 <p className="text-2xl font-bold text-white"> 47 {totalStaked.toLocaleString()} YANI 48 </p> 49 </div> 50 <div className="p-6 bg-gray-900 rounded-xl"> 51 <div className="flex items-center gap-3 mb-2"> 52 <TrendingUp className="w-5 h-5 text-emerald-400" /> 53 <span className="text-gray-400">Pending Rewards</span> 54 </div> 55 <p className="text-2xl font-bold text-white"> 56 {totalRewards.toFixed(2)} YANI 57 </p> 58 </div> 59 <div className="p-6 bg-gray-900 rounded-xl"> 60 <div className="flex items-center gap-3 mb-2"> 61 <Lock className="w-5 h-5 text-purple-400" /> 62 <span className="text-gray-400">Active Positions</span> 63 </div> 64 <p className="text-2xl font-bold text-white">{positions.length}</p> 65 </div> 66 </div> 67 68 {/* Available Pools */} 69 <h2 className="text-xl font-bold text-white mb-4">Staking Pools</h2> 70 <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8"> 71 {pools.map((pool) => ( 72 <div 73 key={pool.id} 74 className="p-6 bg-gray-900 rounded-xl border border-gray-800 hover:border-cyan-500 transition-colors cursor-pointer" 75 > 76 <h3 className="font-semibold text-white mb-2">{pool.name}</h3> 77 <p className="text-3xl font-bold text-cyan-400 mb-1">{pool.apy}%</p> 78 <p className="text-sm text-gray-400 mb-4"> 79 {pool.lockPeriod > 0 ? `${pool.lockPeriod} day lock` : 'Flexible'} 80 </p> 81 <div className="flex justify-between text-sm text-gray-400"> 82 <span>TVL</span> 83 <span>${(parseFloat(pool.tvl) / 1000000).toFixed(1)}M</span> 84 </div> 85 </div> 86 ))} 87 </div> 88 89 {/* My Positions */} 90 <h2 className="text-xl font-bold text-white mb-4">My Positions</h2> 91 {positions.length === 0 ? ( 92 <p className="text-gray-400">No staking positions yet</p> 93 ) : ( 94 <div className="flex flex-col gap-4"> 95 {positions.map((position) => ( 96 <div 97 key={position.id} 98 className="p-6 bg-gray-900 rounded-xl flex items-center justify-between" 99 > 100 <div> 101 <p className="font-semibold text-white">{position.amount} YANI</p> 102 <p className="text-sm text-gray-400"> 103 Rewards: {position.rewards} YANI 104 </p> 105 </div> 106 <div className="flex gap-2"> 107 <button 108 onClick={() => claim(position.id)} 109 className="px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm" 110 > 111 Claim 112 </button> 113 <button 114 onClick={() => unstake(position.id, position.amount)} 115 className="px-4 py-2 bg-gray-700 text-white rounded-lg text-sm" 116 > 117 Unstake 118 </button> 119 </div> 120 </div> 121 ))} 122 </div> 123 )} 124 </div> 125 ); 126 }
Portfolio Tracker
Track wallet balances and transaction history:
examples/Portfolio.tsxtsx
1 'use client'; 2 3 import { useState, useEffect } from 'react'; 4 import { apiGet } from '@/lib/api/client'; 5 import type { WalletBalance } from '@/apps/platform/api/contracts/wallets'; 6 import { Wallet, ArrowUpRight, ArrowDownLeft } from 'lucide-react'; 7 8 interface PortfolioData { 9 balances: WalletBalance[]; 10 totalValueUsd: number; 11 } 12 interface Transaction { hash: string; type: 'send' | 'receive'; amount: string; token: string; timestamp: string; } 13 14 export function Portfolio() { 15 const [portfolio, setPortfolio] = useState<PortfolioData | null>(null); 16 const [transactions, setTransactions] = useState<Transaction[]>([]); 17 18 useEffect(() => { 19 apiGet<PortfolioData>('/api/dashboard/wallets').then(setPortfolio).catch(console.error); 20 apiGet<{ transactions: Transaction[] }>('/api/dashboard/transactions?limit=10') 21 .then(d => setTransactions(d.transactions)) 22 .catch(console.error); 23 }, []); 24 25 const { balances = [], totalValueUsd = 0 } = portfolio ?? {}; 26 27 return ( 28 <div className="max-w-2xl mx-auto p-6"> 29 {/* Total Value */} 30 <div className="text-center mb-8"> 31 <p className="text-gray-400 mb-2">Total Portfolio Value</p> 32 <p className="text-4xl font-bold text-white"> 33 ${totalValueUsd.toLocaleString()} 34 </p> 35 </div> 36 37 {/* Token Balances */} 38 <h2 className="text-lg font-semibold text-white mb-4">Assets</h2> 39 <div className="flex flex-col gap-3 mb-8"> 40 {balances.map((token) => ( 41 <div 42 key={token.symbol} 43 className="flex items-center justify-between p-4 bg-gray-900 rounded-xl" 44 > 45 <div className="flex items-center gap-3"> 46 <img 47 src={token.logoUrl} 48 alt={token.symbol} 49 className="w-10 h-10 rounded-full" 50 /> 51 <div> 52 <p className="font-semibold text-white">{token.symbol}</p> 53 <p className="text-sm text-gray-400">{token.name}</p> 54 </div> 55 </div> 56 <div className="text-right"> 57 <p className="font-semibold text-white">{token.balance}</p> 58 <p className="text-sm text-gray-400"> 59 ${(parseFloat(token.balance) * token.priceUsd).toFixed(2)} 60 </p> 61 </div> 62 </div> 63 ))} 64 </div> 65 66 {/* Recent Transactions */} 67 <h2 className="text-lg font-semibold text-white mb-4">Recent Activity</h2> 68 <div className="flex flex-col gap-3"> 69 {transactions.map((tx) => ( 70 <div 71 key={tx.hash} 72 className="flex items-center justify-between p-4 bg-gray-900 rounded-xl" 73 > 74 <div className="flex items-center gap-3"> 75 {tx.type === 'send' ? ( 76 <ArrowUpRight className="w-5 h-5 text-red-400" /> 77 ) : ( 78 <ArrowDownLeft className="w-5 h-5 text-emerald-400" /> 79 )} 80 <div> 81 <p className="font-semibold text-white capitalize">{tx.type}</p> 82 <p className="text-sm text-gray-400"> 83 {new Date(tx.timestamp).toLocaleDateString()} 84 </p> 85 </div> 86 </div> 87 <div className="text-right"> 88 <p className={`font-semibold ${ 89 tx.type === 'send' ? 'text-red-400' : 'text-emerald-400' 90 }`}> 91 {tx.type === 'send' ? '-' : '+'}{tx.amount} {tx.token} 92 </p> 93 </div> 94 </div> 95 ))} 96 </div> 97 </div> 98 ); 99 }
Governance UI
Display and vote on DAO proposals:
examples/Governance.tsxtsx
1 'use client'; 2 3 import { useState, useEffect } from 'react'; 4 import { apiGet, apiPost } from '@/lib/api/client'; 5 import { Vote, Clock, CheckCircle, XCircle } from 'lucide-react'; 6 7 interface Proposal { 8 id: string; title: string; description: string; status: string; 9 forVotes: string; againstVotes: string; abstainVotes: string; endTime: string; 10 } 11 12 export function Governance() { 13 const [proposals, setProposals] = useState<Proposal[]>([]); 14 const [isLoading, setIsLoading] = useState(true); 15 const [votedIds, setVotedIds] = useState<Set<string>>(new Set()); 16 17 useEffect(() => { 18 apiGet<{ proposals: Proposal[] }>('/api/blockchain/governance/proposals?status=active') 19 .then(d => setProposals(d.proposals)) 20 .finally(() => setIsLoading(false)); 21 }, []); 22 23 const vote = async (proposalId: string, voteType: 'for' | 'against' | 'abstain') => { 24 await apiPost('/api/blockchain/governance/vote', { proposalId, voteType }); 25 setVotedIds(prev => new Set([...prev, proposalId])); 26 }; 27 28 const hasVoted = (proposalId: string) => votedIds.has(proposalId); 29 30 const handleVote = async (proposalId: string, voteType: 'for' | 'against' | 'abstain') => { 31 await vote(proposalId, voteType); 32 }; 33 34 if (isLoading) return <div>Loading proposals...</div>; 35 36 return ( 37 <div className="max-w-3xl mx-auto p-6"> 38 <h1 className="text-2xl font-bold text-white mb-6">Active Proposals</h1> 39 40 <div className="flex flex-col gap-6"> 41 {proposals.map((proposal) => { 42 const totalVotes = 43 parseFloat(proposal.forVotes) + 44 parseFloat(proposal.againstVotes) + 45 parseFloat(proposal.abstainVotes); 46 const forPercent = totalVotes > 0 47 ? (parseFloat(proposal.forVotes) / totalVotes) * 100 48 : 0; 49 50 return ( 51 <div 52 key={proposal.id} 53 className="p-6 bg-gray-900 rounded-xl border border-gray-800" 54 > 55 <div className="flex items-start justify-between mb-4"> 56 <div> 57 <h2 className="text-lg font-semibold text-white mb-1"> 58 {proposal.title} 59 </h2> 60 <div className="flex items-center gap-2 text-sm text-gray-400"> 61 <Clock className="w-4 h-4" /> 62 <span> 63 Ends {new Date(proposal.endTime).toLocaleDateString()} 64 </span> 65 </div> 66 </div> 67 <span className="px-3 py-1 bg-blue-500/20 text-blue-400 rounded-full text-sm"> 68 {proposal.status} 69 </span> 70 </div> 71 72 <p className="text-gray-400 mb-4 line-clamp-2"> 73 {proposal.description} 74 </p> 75 76 {/* Vote Progress */} 77 <div className="mb-4"> 78 <div className="flex justify-between text-sm mb-2"> 79 <span className="text-emerald-400"> 80 For: {forPercent.toFixed(1)}% 81 </span> 82 <span className="text-gray-400"> 83 {(totalVotes / 1000000).toFixed(1)}M votes 84 </span> 85 </div> 86 <div className="h-2 bg-gray-800 rounded-full overflow-hidden"> 87 <div 88 className="h-full bg-emerald-500" 89 style={{ width: `${forPercent}%` }} 90 /> 91 </div> 92 </div> 93 94 {/* Vote Buttons */} 95 {!hasVoted(proposal.id) ? ( 96 <div className="flex gap-3"> 97 <button 98 onClick={() => handleVote(proposal.id, 'for')} 99 className="flex-1 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg flex items-center justify-center gap-2" 100 > 101 <CheckCircle className="w-4 h-4" /> 102 For 103 </button> 104 <button 105 onClick={() => handleVote(proposal.id, 'against')} 106 className="flex-1 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg flex items-center justify-center gap-2" 107 > 108 <XCircle className="w-4 h-4" /> 109 Against 110 </button> 111 <button 112 onClick={() => handleVote(proposal.id, 'abstain')} 113 className="flex-1 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg" 114 > 115 Abstain 116 </button> 117 </div> 118 ) : ( 119 <p className="text-center text-gray-400"> 120 You have already voted on this proposal 121 </p> 122 )} 123 </div> 124 ); 125 })} 126 </div> 127 </div> 128 ); 129 }
Need More Examples?
Browse our GitHub repository for more code examples and full application templates.
View on GitHubLast updated: 2026-04-01