Using the AMM
Master token swaps, liquidity pools, and the automated market maker protocol on YaniPay.
Introduction
The YaniPay Automated Market Maker (AMM) enables decentralized token swaps without traditional order books. This guide covers everything from basic swaps to advanced liquidity provision strategies.
What you'll learn
- How AMMs work and the constant product formula
- Performing token swaps with optimal slippage
- Providing liquidity and earning fees
- Understanding and minimizing price impact
AMM Basics
YaniPay's AMM uses the constant product formula (x * y = k) to determine exchange rates. This ensures that liquidity is always available, regardless of trade size.
0.3% Fee
Per swap transaction
50+ Pools
Active liquidity pools
x * y = k
Constant product formula
Performing Token Swaps
Here's how to execute a token swap using the YaniPay DEX API:
1 // Type definitions for swap operations 2 interface SwapQuote { 3 fromToken: string; 4 toToken: string; 5 fromAmount: string; 6 toAmount: string; 7 priceImpact: string; 8 route: string[]; 9 fee: string; 10 expiresAt: string; 11 } 12 13 interface SwapResult { 14 transactionHash: string; 15 fromAmount: string; 16 toAmount: string; 17 fee: string; 18 timestamp: string; 19 } 20 21 // Get a quote for a swap 22 export async function getSwapQuote( 23 fromToken: string, 24 toToken: string, 25 amount: string 26 ): Promise<SwapQuote> { 27 const response = await fetch('/api/defi/dex/quote', { 28 method: 'POST', 29 headers: { 'Content-Type': 'application/json' }, 30 body: JSON.stringify({ fromToken, toToken, amount }), 31 }); 32 33 if (!response.ok) { 34 throw new Error('Failed to get swap quote'); 35 } 36 37 return response.json(); 38 } 39 40 // Execute the swap 41 export async function executeSwap( 42 fromToken: string, 43 toToken: string, 44 amount: string, 45 slippageTolerance: number = 0.5 // 0.5% 46 ): Promise<SwapResult> { 47 // First get a quote 48 const quote = await getSwapQuote(fromToken, toToken, amount); 49 50 // Calculate minimum output with slippage 51 const minOutput = parseFloat(quote.toAmount) * (1 - slippageTolerance / 100); 52 53 const response = await fetch('/api/defi/dex/swap', { 54 method: 'POST', 55 headers: { 'Content-Type': 'application/json' }, 56 credentials: 'include', 57 body: JSON.stringify({ 58 fromToken, 59 toToken, 60 amount, 61 minOutput: minOutput.toString(), 62 deadline: Date.now() + 300000, // 5 minutes 63 }), 64 }); 65 66 if (!response.ok) { 67 throw new Error('Swap failed'); 68 } 69 70 return response.json(); 71 }
Building a Swap Component
Here's a complete React component for token swapping:
1 'use client'; 2 3 import { useState, useEffect } from 'react'; 4 import { ArrowDownUp, Loader2 } from 'lucide-react'; 5 import { getSwapQuote, executeSwap } from '@/lib/api/swap'; 6 7 const tokens = [ 8 { symbol: 'YANI', name: 'YaniCoin', decimals: 18 }, 9 { symbol: 'ETH', name: 'Ethereum', decimals: 18 }, 10 { symbol: 'USDC', name: 'USD Coin', decimals: 6 }, 11 { symbol: 'BTC', name: 'Bitcoin', decimals: 8 }, 12 ]; 13 14 export function SwapWidget() { 15 const [fromToken, setFromToken] = useState('YANI'); 16 const [toToken, setToToken] = useState('USDC'); 17 const [amount, setAmount] = useState(''); 18 const [quote, setQuote] = useState<SwapQuote | null>(null); 19 const [isLoading, setIsLoading] = useState(false); 20 const [isSwapping, setIsSwapping] = useState(false); 21 22 // Fetch quote when inputs change 23 useEffect(() => { 24 if (!amount || parseFloat(amount) <= 0) { 25 setQuote(null); 26 return; 27 } 28 29 const fetchQuote = async () => { 30 setIsLoading(true); 31 try { 32 const q = await getSwapQuote(fromToken, toToken, amount); 33 setQuote(q); 34 } catch (err) { 35 console.error('Quote error:', err); 36 setQuote(null); 37 } finally { 38 setIsLoading(false); 39 } 40 }; 41 42 const debounce = setTimeout(fetchQuote, 500); 43 return () => clearTimeout(debounce); 44 }, [fromToken, toToken, amount]); 45 46 const handleSwap = async () => { 47 if (!quote) return; 48 49 setIsSwapping(true); 50 try { 51 const result = await executeSwap(fromToken, toToken, amount); 52 alert(`Swap successful! TX: ${result.transactionHash}`); 53 setAmount(''); 54 setQuote(null); 55 } catch (err) { 56 alert('Swap failed. Please try again.'); 57 } finally { 58 setIsSwapping(false); 59 } 60 }; 61 62 const switchTokens = () => { 63 setFromToken(toToken); 64 setToToken(fromToken); 65 setAmount(''); 66 }; 67 68 return ( 69 <div className="p-6 rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 max-w-md mx-auto"> 70 <h3 className="text-lg font-semibold mb-4">Swap Tokens</h3> 71 72 {/* From Token */} 73 <div className="p-4 rounded-lg bg-gray-50 dark:bg-gray-800 mb-2"> 74 <div className="flex justify-between mb-2"> 75 <span className="text-sm text-gray-500">From</span> 76 <span className="text-sm text-gray-500">Balance: 1,000.00</span> 77 </div> 78 <div className="flex gap-3"> 79 <input 80 type="number" 81 value={amount} 82 onChange={(e) => setAmount(e.target.value)} 83 placeholder="0.0" 84 className="flex-1 bg-transparent text-2xl font-medium outline-none" 85 /> 86 <select 87 value={fromToken} 88 onChange={(e) => setFromToken(e.target.value)} 89 className="px-3 py-2 rounded-lg bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600" 90 > 91 {tokens.map((t) => ( 92 <option key={t.symbol} value={t.symbol}>{t.symbol}</option> 93 ))} 94 </select> 95 </div> 96 </div> 97 98 {/* Switch Button */} 99 <div className="flex justify-center -my-2 relative z-10"> 100 <button 101 onClick={switchTokens} 102 className="p-2 rounded-full bg-purple-100 dark:bg-purple-900 hover:bg-purple-200 dark:hover:bg-purple-800 transition-colors" 103 > 104 <ArrowDownUp className="w-5 h-5 text-purple-600 dark:text-purple-400" /> 105 </button> 106 </div> 107 108 {/* To Token */} 109 <div className="p-4 rounded-lg bg-gray-50 dark:bg-gray-800 mt-2"> 110 <div className="flex justify-between mb-2"> 111 <span className="text-sm text-gray-500">To</span> 112 </div> 113 <div className="flex gap-3"> 114 <div className="flex-1 text-2xl font-medium"> 115 {isLoading ? ( 116 <Loader2 className="w-6 h-6 animate-spin" /> 117 ) : ( 118 quote?.toAmount || '0.0' 119 )} 120 </div> 121 <select 122 value={toToken} 123 onChange={(e) => setToToken(e.target.value)} 124 className="px-3 py-2 rounded-lg bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600" 125 > 126 {tokens.map((t) => ( 127 <option key={t.symbol} value={t.symbol}>{t.symbol}</option> 128 ))} 129 </select> 130 </div> 131 </div> 132 133 {/* Quote Details */} 134 {quote && ( 135 <div className="mt-4 p-3 rounded-lg bg-gray-50 dark:bg-gray-800 flex flex-col gap-2 text-sm"> 136 <div className="flex justify-between"> 137 <span className="text-gray-500">Rate</span> 138 <span>1 {fromToken} = {(parseFloat(quote.toAmount) / parseFloat(amount)).toFixed(4)} {toToken}</span> 139 </div> 140 <div className="flex justify-between"> 141 <span className="text-gray-500">Price Impact</span> 142 <span className={parseFloat(quote.priceImpact) > 3 ? 'text-red-500' : ''}> 143 {quote.priceImpact}% 144 </span> 145 </div> 146 <div className="flex justify-between"> 147 <span className="text-gray-500">Fee</span> 148 <span>{quote.fee} {fromToken}</span> 149 </div> 150 </div> 151 )} 152 153 {/* Swap Button */} 154 <button 155 onClick={handleSwap} 156 disabled={!quote || isSwapping} 157 className="w-full mt-4 py-3 rounded-lg bg-purple-600 hover:bg-purple-700 disabled:bg-gray-400 text-white font-medium transition-colors" 158 > 159 {isSwapping ? ( 160 <span className="flex items-center justify-center gap-2"> 161 <Loader2 className="w-5 h-5 animate-spin" /> 162 Swapping... 163 </span> 164 ) : ( 165 'Swap' 166 )} 167 </button> 168 </div> 169 ); 170 }
Liquidity Pools
Liquidity providers earn fees from every swap. Here's how to add and remove liquidity:
1 interface Pool { 2 id: string; 3 tokenA: string; 4 tokenB: string; 5 reserveA: string; 6 reserveB: string; 7 totalLiquidity: string; 8 apr: string; 9 volume24h: string; 10 } 11 12 interface LiquidityPosition { 13 poolId: string; 14 liquidity: string; 15 sharePercent: string; 16 tokenAAmount: string; 17 tokenBAmount: string; 18 earnedFees: string; 19 } 20 21 // Get all available pools 22 export async function getPools(): Promise<Pool[]> { 23 const response = await fetch('/api/defi/dex/pools'); 24 if (!response.ok) throw new Error('Failed to fetch pools'); 25 return response.json(); 26 } 27 28 // Get user's liquidity positions 29 export async function getPositions(): Promise<LiquidityPosition[]> { 30 const response = await fetch('/api/defi/dex/positions', { 31 credentials: 'include', 32 }); 33 if (!response.ok) throw new Error('Failed to fetch positions'); 34 return response.json(); 35 } 36 37 // Add liquidity to a pool 38 export async function addLiquidity( 39 tokenA: string, 40 tokenB: string, 41 amountA: string, 42 amountB: string, 43 slippage: number = 0.5 44 ): Promise<{ transactionHash: string; liquidity: string }> { 45 const response = await fetch('/api/defi/dex/add-liquidity', { 46 method: 'POST', 47 headers: { 'Content-Type': 'application/json' }, 48 credentials: 'include', 49 body: JSON.stringify({ 50 tokenA, 51 tokenB, 52 amountA, 53 amountB, 54 slippage, 55 }), 56 }); 57 58 if (!response.ok) throw new Error('Failed to add liquidity'); 59 return response.json(); 60 } 61 62 // Remove liquidity from a pool 63 export async function removeLiquidity( 64 poolId: string, 65 liquidity: string, 66 slippage: number = 0.5 67 ): Promise<{ transactionHash: string; amountA: string; amountB: string }> { 68 const response = await fetch('/api/defi/dex/remove-liquidity', { 69 method: 'POST', 70 headers: { 'Content-Type': 'application/json' }, 71 credentials: 'include', 72 body: JSON.stringify({ poolId, liquidity, slippage }), 73 }); 74 75 if (!response.ok) throw new Error('Failed to remove liquidity'); 76 return response.json(); 77 }
Understanding Price Impact
Price impact is the difference between the expected price and the execution price, caused by your trade's effect on the pool's reserves. Large trades relative to pool size will have higher impact.
Price Impact Thresholds
- < 1%: Low impact - safe to proceed
- 1-3%: Medium impact - consider splitting the trade
- > 3%: High impact - strongly consider alternative routes
- > 10%: Very high impact - trade will likely be rejected
Advanced Features
Multi-hop Routing
For pairs with low liquidity, the AMM can route through multiple pools to get better rates:
1 interface Route { 2 path: string[]; 3 expectedOutput: string; 4 priceImpact: string; 5 totalFee: string; 6 } 7 8 // Find the best route for a swap 9 export async function findBestRoute( 10 fromToken: string, 11 toToken: string, 12 amount: string 13 ): Promise<Route[]> { 14 const response = await fetch('/api/defi/dex/routes', { 15 method: 'POST', 16 headers: { 'Content-Type': 'application/json' }, 17 body: JSON.stringify({ fromToken, toToken, amount }), 18 }); 19 20 if (!response.ok) throw new Error('Failed to find routes'); 21 22 const routes = await response.json(); 23 24 // Routes are already sorted by best output 25 return routes; 26 } 27 28 // Example: YANI -> ETH might route through 29 // YANI -> USDC -> ETH for better rates
Limit Orders
Set orders that execute when a specific price is reached:
1 interface LimitOrder { 2 id: string; 3 fromToken: string; 4 toToken: string; 5 amount: string; 6 targetPrice: string; 7 status: 'pending' | 'filled' | 'cancelled'; 8 createdAt: string; 9 expiresAt: string; 10 } 11 12 // Create a limit order 13 export async function createLimitOrder( 14 fromToken: string, 15 toToken: string, 16 amount: string, 17 targetPrice: string, 18 expiresIn: number = 86400 // 24 hours default 19 ): Promise<LimitOrder> { 20 const response = await fetch('/api/defi/dex/limit-orders', { 21 method: 'POST', 22 headers: { 'Content-Type': 'application/json' }, 23 credentials: 'include', 24 body: JSON.stringify({ 25 fromToken, 26 toToken, 27 amount, 28 targetPrice, 29 expiresIn, 30 }), 31 }); 32 33 if (!response.ok) throw new Error('Failed to create limit order'); 34 return response.json(); 35 }
Best Practices
Always check price impact
For trades over $1000, always verify the price impact before confirming.
Use appropriate slippage
0.5% is good for stable pairs, use 1-3% for volatile tokens.
Monitor impermanent loss
When providing liquidity, track your position's value vs holding the tokens.
Beware of sandwich attacks
Use private transaction pools for large trades to avoid MEV exploitation.
Next Steps
Now that you understand the AMM, explore these related topics: