Wallet Integration
Learn how to integrate multi-asset crypto wallets into your application with YaniPay's Wallet API.
Introduction
The YaniPay Wallet API allows you to manage multi-asset crypto wallets for your users. This guide will walk you through fetching wallet data, displaying balances, and building a reusable wallet component.
What you'll learn
- Fetching wallet data from the API
- Displaying multi-asset balances
- Building a reusable wallet component
- Handling loading and error states
Prerequisites
Before starting this guide, make sure you have:
- Completed the Quick Start Guide
- A running YaniPay development server
- Basic knowledge of React and TypeScript
Fetching User Wallets
The Wallet API endpoint returns all wallets associated with a user, including their balances across multiple cryptocurrencies.
1 // Type definitions for wallet data 2 interface Wallet { 3 id: string; 4 userId: string; 5 currency: string; 6 symbol: string; 7 balance: string; 8 lockedBalance: string; 9 network: string; 10 address: string; 11 createdAt: string; 12 updatedAt: string; 13 } 14 15 interface WalletsResponse { 16 wallets: Wallet[]; 17 totalValue: { 18 EUR: string; 19 USD: string; 20 }; 21 } 22 23 // Fetch wallets for the authenticated user 24 export async function fetchWallets(): Promise<WalletsResponse> { 25 const response = await fetch('/api/defi/wallets', { 26 method: 'GET', 27 headers: { 28 'Content-Type': 'application/json', 29 }, 30 credentials: 'include', // Include auth cookies 31 }); 32 33 if (!response.ok) { 34 throw new Error('Failed to fetch wallets'); 35 } 36 37 return response.json(); 38 }
Displaying Balances
Once you have the wallet data, you can display it in your UI. Here's a simple React hook for managing wallet state:
1 import { useState, useEffect } from 'react'; 2 import { fetchWallets, type Wallet, type WalletsResponse } from '@/lib/api/wallets'; 3 4 interface UseWalletsResult { 5 wallets: Wallet[]; 6 totalValue: WalletsResponse['totalValue'] | null; 7 isLoading: boolean; 8 error: Error | null; 9 refetch: () => Promise<void>; 10 } 11 12 export function useWallets(): UseWalletsResult { 13 const [wallets, setWallets] = useState<Wallet[]>([]); 14 const [totalValue, setTotalValue] = useState<WalletsResponse['totalValue'] | null>(null); 15 const [isLoading, setIsLoading] = useState(true); 16 const [error, setError] = useState<Error | null>(null); 17 18 const fetchData = async () => { 19 setIsLoading(true); 20 setError(null); 21 22 try { 23 const data = await fetchWallets(); 24 setWallets(data.wallets); 25 setTotalValue(data.totalValue); 26 } catch (err) { 27 setError(err instanceof Error ? err : new Error('Unknown error')); 28 } finally { 29 setIsLoading(false); 30 } 31 }; 32 33 useEffect(() => { 34 fetchData(); 35 }, []); 36 37 return { 38 wallets, 39 totalValue, 40 isLoading, 41 error, 42 refetch: fetchData, 43 }; 44 }
Building a Wallet Component
Here's a complete example of a wallet balance component that displays all user wallets:
1 'use client'; 2 3 import { useWallets } from '@/hooks/useWallets'; 4 import { Wallet, RefreshCw, AlertCircle } from 'lucide-react'; 5 6 // Format currency with locale 7 function formatBalance(balance: string, decimals = 8): string { 8 const num = parseFloat(balance); 9 return num.toLocaleString('fr-FR', { 10 minimumFractionDigits: 2, 11 maximumFractionDigits: decimals, 12 }); 13 } 14 15 export function WalletBalance() { 16 const { wallets, totalValue, isLoading, error, refetch } = useWallets(); 17 18 if (isLoading) { 19 return ( 20 <div className="animate-pulse flex flex-col gap-4"> 21 <div className="h-8 bg-gray-200 dark:bg-gray-800 rounded w-1/3" /> 22 <div className="h-24 bg-gray-200 dark:bg-gray-800 rounded" /> 23 </div> 24 ); 25 } 26 27 if (error) { 28 return ( 29 <div className="p-4 rounded-lg bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800"> 30 <div className="flex items-center gap-2 text-red-600 dark:text-red-400"> 31 <AlertCircle className="w-5 h-5" /> 32 <span>Failed to load wallets: {error.message}</span> 33 </div> 34 <button 35 onClick={refetch} 36 className="mt-2 text-sm text-red-600 dark:text-red-400 hover:underline" 37 > 38 Try again 39 </button> 40 </div> 41 ); 42 } 43 44 return ( 45 <div className="flex flex-col gap-6"> 46 {/* Total Value */} 47 <div className="p-6 rounded-lg bg-gradient-to-r from-emerald-500 to-cyan-500 text-white"> 48 <div className="flex items-center justify-between mb-2"> 49 <span className="text-sm opacity-80">Total Portfolio Value</span> 50 <button 51 onClick={refetch} 52 className="p-1.5 rounded-full hover:bg-white/20 transition-colors" 53 > 54 <RefreshCw className="w-4 h-4" /> 55 </button> 56 </div> 57 <div className="text-3xl font-bold"> 58 €{formatBalance(totalValue?.EUR || '0', 2)} 59 </div> 60 <div className="text-sm opacity-80 mt-1"> 61 ≈ ${formatBalance(totalValue?.USD || '0', 2)} 62 </div> 63 </div> 64 65 {/* Wallet List */} 66 <div className="flex flex-col gap-3"> 67 <h3 className="font-semibold text-gray-900 dark:text-gray-100">Your Assets</h3> 68 {wallets.map((wallet) => ( 69 <div 70 key={wallet.id} 71 className="flex items-center justify-between p-4 rounded-lg border border-gray-200 dark:border-gray-800" 72 > 73 <div className="flex items-center gap-3"> 74 <div className="w-10 h-10 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center"> 75 <Wallet className="w-5 h-5 text-gray-600 dark:text-gray-400" /> 76 </div> 77 <div> 78 <div className="font-medium text-gray-900 dark:text-gray-100"> 79 {wallet.currency} 80 </div> 81 <div className="text-sm text-gray-500 dark:text-gray-400"> 82 {wallet.symbol} • {wallet.network} 83 </div> 84 </div> 85 </div> 86 <div className="text-right"> 87 <div className="font-medium text-gray-900 dark:text-gray-100"> 88 {formatBalance(wallet.balance)} {wallet.symbol} 89 </div> 90 {parseFloat(wallet.lockedBalance) > 0 && ( 91 <div className="text-sm text-orange-600 dark:text-orange-400"> 92 {formatBalance(wallet.lockedBalance)} locked 93 </div> 94 )} 95 </div> 96 </div> 97 ))} 98 </div> 99 </div> 100 ); 101 }
Best Practices
Cache wallet data
Use SWR or React Query to cache wallet data and avoid unnecessary API calls.
Handle decimals correctly
Always use the correct number of decimals for each cryptocurrency (e.g., 18 for YANI, 8 for BTC).
Security reminder
Never expose private keys or seed phrases in your frontend code. All sensitive operations should be handled server-side.
Next Steps
Now that you've integrated wallets, continue with these guides: