Building a DApp
Build a complete decentralized application with YaniPay from scratch.
Introduction
In this guide, you'll build a complete DeFi dashboard that allows users to view their portfolio, swap tokens, and stake YANI. By the end, you'll have a production-ready application that integrates all major YaniPay features.
What you'll build
- Portfolio dashboard with real-time balances
- Token swap interface with price quotes
- Staking dashboard with rewards tracking
- Transaction history and notifications
Architecture YaniChain
Votre DApp sera construite sur YaniChain, la blockchain propriétaire de YaniPay, et alimentée par l'IA Y.A.N.I. pour une expérience utilisateur optimisée.
YaniChain Infrastructure
- 50,000+ TPS pour une scalabilité maximale
- Finalité en 6 secondes
- Gas à $0.001 par transaction
Y.A.N.I. Intelligence
- Routing optimal automatique
- Détection de fraude temps réel
- Recommandations personnalisées
Avantage développeur
Project Structure
We'll organize our DApp with a clean, scalable structure:
my-defi-dapp/
├── app/
│ ├── layout.tsx # Root layout with providers
│ ├── page.tsx # Homepage
│ ├── dashboard/
│ │ ├── page.tsx # Portfolio dashboard
│ │ ├── swap/
│ │ │ └── page.tsx # Token swap
│ │ └── staking/
│ │ └── page.tsx # Staking interface
│ └── api/ # API routes (optional proxy)
├── components/
│ ├── ui/ # Reusable UI components
│ ├── wallet/ # Wallet-related components
│ ├── swap/ # Swap-related components
│ └── staking/ # Staking components
├── hooks/
│ ├── useWallets.ts # Wallet data hook
│ ├── useSwap.ts # Swap operations hook
│ └── useStaking.ts # Staking operations hook
├── lib/
│ ├── api/ # API client functions
│ └── utils.ts # Utility functions
└── types/
└── index.ts # TypeScript typesSetting Up API Integration
First, create a centralized API client to communicate with YaniPay:
1 const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api'; 2 3 interface ApiOptions extends RequestInit { 4 params?: Record<string, string>; 5 } 6 7 class ApiClient { 8 private baseUrl: string; 9 10 constructor(baseUrl: string) { 11 this.baseUrl = baseUrl; 12 } 13 14 async request<T>(endpoint: string, options: ApiOptions = {}): Promise<T> { 15 const { params, ...fetchOptions } = options; 16 17 let url = `${this.baseUrl}${endpoint}`; 18 if (params) { 19 const searchParams = new URLSearchParams(params); 20 url += `?${searchParams.toString()}`; 21 } 22 23 const response = await fetch(url, { 24 ...fetchOptions, 25 headers: { 26 'Content-Type': 'application/json', 27 ...fetchOptions.headers, 28 }, 29 credentials: 'include', 30 }); 31 32 if (!response.ok) { 33 const error = await response.json().catch(() => ({})); 34 throw new Error(error.message || 'API request failed'); 35 } 36 37 return response.json(); 38 } 39 40 get<T>(endpoint: string, params?: Record<string, string>) { 41 return this.request<T>(endpoint, { method: 'GET', params }); 42 } 43 44 post<T>(endpoint: string, data?: unknown) { 45 return this.request<T>(endpoint, { 46 method: 'POST', 47 body: JSON.stringify(data), 48 }); 49 } 50 } 51 52 export const api = new ApiClient(API_BASE);
Building the UI
Create a dashboard layout with navigation:
1 'use client'; 2 3 import Link from 'next/link'; 4 import { usePathname } from 'next/navigation'; 5 import { LayoutDashboard, ArrowLeftRight, Coins, History } from 'lucide-react'; 6 import { cn } from '@/lib/utils'; 7 8 const navItems = [ 9 { href: '/dashboard', label: 'Portfolio', icon: LayoutDashboard }, 10 { href: '/dashboard/swap', label: 'Swap', icon: ArrowLeftRight }, 11 { href: '/dashboard/staking', label: 'Staking', icon: Coins }, 12 { href: '/dashboard/history', label: 'History', icon: History }, 13 ]; 14 15 export default function DashboardLayout({ 16 children, 17 }: { 18 children: React.ReactNode; 19 }) { 20 const pathname = usePathname(); 21 22 return ( 23 <div className="min-h-screen bg-gray-50 dark:bg-gray-950"> 24 {/* Header */} 25 <header className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800"> 26 <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> 27 <div className="flex h-16 items-center justify-between"> 28 <Link href="/" className="text-xl font-bold text-emerald-600"> 29 MyDeFiApp 30 </Link> 31 <nav className="flex items-center gap-1"> 32 {navItems.map((item) => { 33 const Icon = item.icon; 34 const isActive = pathname === item.href; 35 return ( 36 <Link 37 key={item.href} 38 href={item.href} 39 className={cn( 40 'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors', 41 isActive 42 ? 'bg-emerald-50 dark:bg-emerald-950 text-emerald-600 dark:text-emerald-400' 43 : 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800' 44 )} 45 > 46 <Icon className="w-4 h-4" /> 47 {item.label} 48 </Link> 49 ); 50 })} 51 </nav> 52 </div> 53 </div> 54 </header> 55 56 {/* Main Content */} 57 <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> 58 {children} 59 </main> 60 </div> 61 ); 62 }
Connecting to Wallet
Create a wallet connection component that fetches user data:
1 'use client'; 2 3 import { createContext, useContext, useEffect, useState } from 'react'; 4 import { api } from '@/lib/api/client'; 5 6 interface Wallet { 7 id: string; 8 currency: string; 9 symbol: string; 10 balance: string; 11 } 12 13 interface WalletContextType { 14 wallets: Wallet[]; 15 isLoading: boolean; 16 error: Error | null; 17 refetch: () => Promise<void>; 18 } 19 20 const WalletContext = createContext<WalletContextType | null>(null); 21 22 export function WalletProvider({ children }: { children: React.ReactNode }) { 23 const [wallets, setWallets] = useState<Wallet[]>([]); 24 const [isLoading, setIsLoading] = useState(true); 25 const [error, setError] = useState<Error | null>(null); 26 27 const fetchWallets = async () => { 28 try { 29 setIsLoading(true); 30 const data = await api.get<{ wallets: Wallet[] }>('/defi/wallets'); 31 setWallets(data.wallets); 32 setError(null); 33 } catch (err) { 34 setError(err instanceof Error ? err : new Error('Failed to fetch wallets')); 35 } finally { 36 setIsLoading(false); 37 } 38 }; 39 40 useEffect(() => { 41 fetchWallets(); 42 }, []); 43 44 return ( 45 <WalletContext.Provider value={{ wallets, isLoading, error, refetch: fetchWallets }}> 46 {children} 47 </WalletContext.Provider> 48 ); 49 } 50 51 export function useWallet() { 52 const context = useContext(WalletContext); 53 if (!context) { 54 throw new Error('useWallet must be used within a WalletProvider'); 55 } 56 return context; 57 }
Adding DeFi Features
Add swap functionality to your DApp:
1 'use client'; 2 3 import { useState } from 'react'; 4 import { ArrowDownUp, Loader2 } from 'lucide-react'; 5 import { api } from '@/lib/api/client'; 6 import { useWallet } from '@/components/wallet/WalletProvider'; 7 8 export default function SwapPage() { 9 const { wallets } = useWallet(); 10 const [fromToken, setFromToken] = useState('YANI'); 11 const [toToken, setToToken] = useState('USDC'); 12 const [amount, setAmount] = useState(''); 13 const [quote, setQuote] = useState<{ outputAmount: string; rate: string } | null>(null); 14 const [isLoading, setIsLoading] = useState(false); 15 16 const getQuote = async () => { 17 if (!amount || parseFloat(amount) <= 0) return; 18 19 setIsLoading(true); 20 try { 21 const data = await api.get<{ outputAmount: string; rate: string }>( 22 '/defi/swap/quote', 23 { fromToken, toToken, amount } 24 ); 25 setQuote(data); 26 } catch (error) { 27 console.error('Failed to get quote:', error); 28 } finally { 29 setIsLoading(false); 30 } 31 }; 32 33 const executeSwap = async () => { 34 if (!quote) return; 35 36 setIsLoading(true); 37 try { 38 await api.post('/defi/swap/execute', { 39 fromToken, 40 toToken, 41 amount, 42 minOutput: quote.outputAmount, 43 slippage: 0.5, 44 }); 45 // Handle success - refresh balances, show notification, etc. 46 } catch (error) { 47 console.error('Swap failed:', error); 48 } finally { 49 setIsLoading(false); 50 } 51 }; 52 53 return ( 54 <div className="max-w-md mx-auto"> 55 <h1 className="text-2xl font-bold mb-6">Swap Tokens</h1> 56 57 <div className="bg-white dark:bg-gray-900 rounded-xl p-6 shadow-sm border border-gray-200 dark:border-gray-800"> 58 {/* From Token Input */} 59 <div className="mb-4"> 60 <label className="block text-sm font-medium mb-2">From</label> 61 <div className="flex gap-2"> 62 <input 63 type="number" 64 value={amount} 65 onChange={(e) => setAmount(e.target.value)} 66 onBlur={getQuote} 67 placeholder="0.0" 68 className="flex-1 px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-transparent" 69 /> 70 <select 71 value={fromToken} 72 onChange={(e) => setFromToken(e.target.value)} 73 className="px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-transparent" 74 > 75 {wallets.map((w) => ( 76 <option key={w.id} value={w.symbol}>{w.symbol}</option> 77 ))} 78 </select> 79 </div> 80 </div> 81 82 {/* Swap Direction Button */} 83 <div className="flex justify-center -my-2"> 84 <button 85 onClick={() => { 86 setFromToken(toToken); 87 setToToken(fromToken); 88 }} 89 className="p-2 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700" 90 > 91 <ArrowDownUp className="w-5 h-5" /> 92 </button> 93 </div> 94 95 {/* To Token Input */} 96 <div className="mt-4"> 97 <label className="block text-sm font-medium mb-2">To</label> 98 <div className="flex gap-2"> 99 <input 100 type="text" 101 value={quote?.outputAmount || ''} 102 readOnly 103 placeholder="0.0" 104 className="flex-1 px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800" 105 /> 106 <select 107 value={toToken} 108 onChange={(e) => setToToken(e.target.value)} 109 className="px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-transparent" 110 > 111 {wallets.map((w) => ( 112 <option key={w.id} value={w.symbol}>{w.symbol}</option> 113 ))} 114 </select> 115 </div> 116 </div> 117 118 {/* Quote Info */} 119 {quote && ( 120 <div className="mt-4 p-3 rounded-lg bg-gray-50 dark:bg-gray-800 text-sm"> 121 <div className="flex justify-between"> 122 <span className="text-gray-500">Rate</span> 123 <span>1 {fromToken} = {quote.rate} {toToken}</span> 124 </div> 125 </div> 126 )} 127 128 {/* Swap Button */} 129 <button 130 onClick={executeSwap} 131 disabled={!quote || isLoading} 132 className="w-full mt-6 py-3 px-4 rounded-lg bg-emerald-600 hover:bg-emerald-700 disabled:bg-gray-300 disabled:cursor-not-allowed text-white font-medium flex items-center justify-center gap-2" 133 > 134 {isLoading ? ( 135 <> 136 <Loader2 className="w-4 h-4 animate-spin" /> 137 Processing... 138 </> 139 ) : ( 140 'Swap' 141 )} 142 </button> 143 </div> 144 </div> 145 ); 146 }
Deployment
When you're ready to deploy, follow these steps:
- Set environment variables - Configure your production API URL and other secrets
- Build the application:
pnpm build - Deploy to production server:
docker compose up -d
Using the AMM
Deep dive into the AMM protocol and liquidity pools
Example Projects
Browse complete example applications