AMM Simulator
Build an interactive constant product AMM (Automated Market Maker) simulator to understand how decentralized exchanges like Raydium and Orca work.
Project Overview
Text
┌─────────────────────────────────────────────────────────────────┐
│ AMM Simulator Interface │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Liquidity Pool │ │
│ │ │ │
│ │ SOL: 1000.00 USDC: 20000.00 │ │
│ │ ════════════ ══════════════ │ │
│ │ │ │
│ │ Constant K = 20,000,000 │ │
│ │ Price: 1 SOL = 20.00 USDC │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────┐ ┌───────────────────────────┐ │
│ │ SWAP │ │ ADD LIQUIDITY │ │
│ │ │ │ │ │
│ │ [SOL ▼] → [USDC ▼] │ │ SOL: [____] │ │
│ │ │ │ USDC: [____] │ │
│ │ Amount: [100___] │ │ │ │
│ │ │ │ LP Tokens: ~XXX │ │
│ │ You receive: 1818.18 │ │ │ │
│ │ Price impact: 9.09% │ │ [ Add Liquidity ] │ │
│ │ Fee: 0.30% │ │ │ │
│ │ │ └───────────────────────────┘ │
│ │ [ Execute Swap ] │ │
│ └───────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Price Curve Visualization │ │
│ │ ● │ │
│ │ ╲ │ │
│ │ ● │ │
│ │ ╲ │ │
│ │ ●●●●●●●●●●●●●●●●●●●●● │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Project Setup
Bash
bunx create-next-app@latest amm-simulator --typescript --tailwind --app
cd amm-simulator
bun add recharts lucide-react
bun dev
AMM Core Logic
TypeScript
// lib/amm.ts
export interface Pool {
reserveA: number; // Token A reserve
reserveB: number; // Token B reserve
totalLpTokens: number; // Total LP tokens issued
feePercent: number; // Swap fee (e.g., 0.3%)
}
export interface SwapResult {
amountOut: number;
priceImpact: number;
fee: number;
newReserveA: number;
newReserveB: number;
effectivePrice: number;
spotPriceBefore: number;
spotPriceAfter: number;
}
export interface LiquidityResult {
lpTokens: number;
actualAmountA: number;
actualAmountB: number;
shareOfPool: number;
}
/**
* Constant Product AMM
*
* The core invariant: x * y = k
*
* Where:
* - x = reserve of token A
* - y = reserve of token B
* - k = constant product (invariant)
*/
export class ConstantProductAMM {
private pool: Pool;
constructor(
initialReserveA: number,
initialReserveB: number,
feePercent: number = 0.3,
) {
this.pool = {
reserveA: initialReserveA,
reserveB: initialReserveB,
totalLpTokens: Math.sqrt(initialReserveA * initialReserveB),
feePercent,
};
}
/**
* Get the constant product (k)
*/
get constantK(): number {
return this.pool.reserveA * this.pool.reserveB;
}
/**
* Get current spot price (A in terms of B)
*/
get spotPrice(): number {
return this.pool.reserveB / this.pool.reserveA;
}
/**
* Get pool state
*/
get state(): Pool {
return { ...this.pool };
}
/**
* Calculate swap output using constant product formula
*
* Formula: (x + Δx) * (y - Δy) = k
* Solving for Δy: Δy = y - k / (x + Δx)
* Δy = y * Δx / (x + Δx)
*/
calculateSwapAToB(amountIn: number): SwapResult {
const { reserveA, reserveB, feePercent } = this.pool;
// Apply fee to input
const fee = amountIn * (feePercent / 100);
const amountInAfterFee = amountIn - fee;
// Calculate output using constant product formula
// Δy = y * Δx / (x + Δx)
const amountOut =
(reserveB * amountInAfterFee) / (reserveA + amountInAfterFee);
// New reserves after swap
const newReserveA = reserveA + amountIn;
const newReserveB = reserveB - amountOut;
// Price calculations
const spotPriceBefore = reserveB / reserveA;
const spotPriceAfter = newReserveB / newReserveA;
const effectivePrice = amountOut / amountIn;
// Price impact = (spot_before - effective) / spot_before
const priceImpact =
((spotPriceBefore - effectivePrice) / spotPriceBefore) * 100;
return {
amountOut,
priceImpact,
fee,
newReserveA,
newReserveB,
effectivePrice,
spotPriceBefore,
spotPriceAfter,
};
}
/**
* Calculate swap from B to A
*/
calculateSwapBToA(amountIn: number): SwapResult {
const { reserveA, reserveB, feePercent } = this.pool;
const fee = amountIn * (feePercent / 100);
const amountInAfterFee = amountIn - fee;
const amountOut =
(reserveA * amountInAfterFee) / (reserveB + amountInAfterFee);
const newReserveA = reserveA - amountOut;
const newReserveB = reserveB + amountIn;
const spotPriceBefore = reserveA / reserveB;
const spotPriceAfter = newReserveA / newReserveB;
const effectivePrice = amountOut / amountIn;
const priceImpact =
((spotPriceBefore - effectivePrice) / spotPriceBefore) * 100;
return {
amountOut,
priceImpact,
fee,
newReserveA,
newReserveB,
effectivePrice,
spotPriceBefore,
spotPriceAfter,
};
}
/**
* Execute a swap and update pool state
*/
executeSwap(amountIn: number, direction: "AtoB" | "BtoA"): SwapResult {
const result =
direction === "AtoB"
? this.calculateSwapAToB(amountIn)
: this.calculateSwapBToA(amountIn);
this.pool.reserveA = result.newReserveA;
this.pool.reserveB = result.newReserveB;
return result;
}
/**
* Calculate LP tokens for adding liquidity
*
* Must add proportional amounts to maintain price
* LP tokens = totalLP * min(amountA/reserveA, amountB/reserveB)
*/
calculateAddLiquidity(amountA: number, amountB: number): LiquidityResult {
const { reserveA, reserveB, totalLpTokens } = this.pool;
// Calculate the ratio
const ratioA = amountA / reserveA;
const ratioB = amountB / reserveB;
// Use the smaller ratio to maintain price
const ratio = Math.min(ratioA, ratioB);
// Calculate actual amounts used (proportional to reserves)
const actualAmountA = ratio === ratioA ? amountA : reserveA * ratio;
const actualAmountB = ratio === ratioB ? amountB : reserveB * ratio;
// LP tokens minted
const lpTokens = totalLpTokens * ratio;
// Share of pool
const shareOfPool = (lpTokens / (totalLpTokens + lpTokens)) * 100;
return {
lpTokens,
actualAmountA,
actualAmountB,
shareOfPool,
};
}
/**
* Add liquidity to the pool
*/
addLiquidity(amountA: number, amountB: number): LiquidityResult {
const result = this.calculateAddLiquidity(amountA, amountB);
this.pool.reserveA += result.actualAmountA;
this.pool.reserveB += result.actualAmountB;
this.pool.totalLpTokens += result.lpTokens;
return result;
}
/**
* Calculate tokens received for removing liquidity
*/
calculateRemoveLiquidity(lpTokens: number): {
amountA: number;
amountB: number;
} {
const { reserveA, reserveB, totalLpTokens } = this.pool;
const share = lpTokens / totalLpTokens;
return {
amountA: reserveA * share,
amountB: reserveB * share,
};
}
/**
* Remove liquidity from the pool
*/
removeLiquidity(lpTokens: number): { amountA: number; amountB: number } {
const result = this.calculateRemoveLiquidity(lpTokens);
this.pool.reserveA -= result.amountA;
this.pool.reserveB -= result.amountB;
this.pool.totalLpTokens -= lpTokens;
return result;
}
/**
* Generate price curve data for visualization
*/
generatePriceCurve(
points: number = 50,
): { reserveA: number; reserveB: number; price: number }[] {
const k = this.constantK;
const minA = this.pool.reserveA * 0.1;
const maxA = this.pool.reserveA * 3;
const step = (maxA - minA) / points;
const data: { reserveA: number; reserveB: number; price: number }[] = [];
for (let a = minA; a <= maxA; a += step) {
const b = k / a;
data.push({
reserveA: a,
reserveB: b,
price: b / a,
});
}
return data;
}
}
Pool Display Component
TypeScript
// components/PoolDisplay.tsx
"use client";
interface PoolDisplayProps {
reserveA: number;
reserveB: number;
tokenA: string;
tokenB: string;
constantK: number;
spotPrice: number;
}
export function PoolDisplay({
reserveA,
reserveB,
tokenA,
tokenB,
constantK,
spotPrice,
}: PoolDisplayProps) {
return (
<div className="bg-gradient-to-r from-blue-500 to-purple-600 rounded-xl p-6 text-white">
<h2 className="text-lg font-semibold mb-4">Liquidity Pool</h2>
<div className="grid grid-cols-2 gap-8">
<div>
<p className="text-blue-200 text-sm">{tokenA}</p>
<p className="text-3xl font-bold">{reserveA.toLocaleString(undefined, { maximumFractionDigits: 2 })}</p>
</div>
<div>
<p className="text-purple-200 text-sm">{tokenB}</p>
<p className="text-3xl font-bold">{reserveB.toLocaleString(undefined, { maximumFractionDigits: 2 })}</p>
</div>
</div>
<div className="mt-6 pt-4 border-t border-white/20 grid grid-cols-2 gap-4 text-sm">
<div>
<p className="text-blue-200">Constant K</p>
<p className="font-mono">{constantK.toLocaleString(undefined, { maximumFractionDigits: 0 })}</p>
</div>
<div>
<p className="text-purple-200">Spot Price</p>
<p className="font-mono">1 {tokenA} = {spotPrice.toFixed(4)} {tokenB}</p>
</div>
</div>
</div>
);
}
Swap Interface
TypeScript
// components/SwapInterface.tsx
"use client";
import { useState, useMemo } from "react";
import { ArrowDownUp } from "lucide-react";
import { ConstantProductAMM, SwapResult } from "@/lib/amm";
interface SwapInterfaceProps {
amm: ConstantProductAMM;
tokenA: string;
tokenB: string;
onSwap: (result: SwapResult, direction: "AtoB" | "BtoA") => void;
}
export function SwapInterface({ amm, tokenA, tokenB, onSwap }: SwapInterfaceProps) {
const [amountIn, setAmountIn] = useState("");
const [direction, setDirection] = useState<"AtoB" | "BtoA">("AtoB");
const fromToken = direction === "AtoB" ? tokenA : tokenB;
const toToken = direction === "AtoB" ? tokenB : tokenA;
const swapResult = useMemo(() => {
const amount = parseFloat(amountIn);
if (isNaN(amount) || amount <= 0) return null;
return direction === "AtoB"
? amm.calculateSwapAToB(amount)
: amm.calculateSwapBToA(amount);
}, [amm, amountIn, direction]);
const handleSwap = () => {
if (!swapResult) return;
const amount = parseFloat(amountIn);
const result = amm.executeSwap(amount, direction);
onSwap(result, direction);
setAmountIn("");
};
const toggleDirection = () => {
setDirection(d => d === "AtoB" ? "BtoA" : "AtoB");
setAmountIn("");
};
return (
<div className="bg-white dark:bg-gray-900 rounded-xl p-6 shadow-lg">
<h3 className="text-lg font-semibold mb-4">Swap</h3>
{/* From Token */}
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 mb-2">
<div className="flex justify-between mb-2">
<span className="text-sm text-gray-500">From</span>
<span className="text-sm text-gray-500">
Balance: {direction === "AtoB"
? amm.state.reserveA.toFixed(2)
: amm.state.reserveB.toFixed(2)} (pool)
</span>
</div>
<div className="flex items-center gap-4">
<input
type="number"
value={amountIn}
onChange={(e) => setAmountIn(e.target.value)}
placeholder="0.00"
className="flex-1 bg-transparent text-2xl font-medium outline-none"
/>
<span className="px-3 py-1 bg-gray-200 dark:bg-gray-700 rounded-lg font-medium">
{fromToken}
</span>
</div>
</div>
{/* Swap Direction Toggle */}
<div className="flex justify-center -my-2 relative z-10">
<button
onClick={toggleDirection}
className="p-2 bg-gray-100 dark:bg-gray-800 rounded-full border-4 border-white dark:border-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
>
<ArrowDownUp className="w-5 h-5" />
</button>
</div>
{/* To Token */}
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 mt-2">
<div className="flex justify-between mb-2">
<span className="text-sm text-gray-500">To (estimated)</span>
</div>
<div className="flex items-center gap-4">
<input
type="text"
value={swapResult ? swapResult.amountOut.toFixed(4) : "0.00"}
readOnly
className="flex-1 bg-transparent text-2xl font-medium outline-none"
/>
<span className="px-3 py-1 bg-gray-200 dark:bg-gray-700 rounded-lg font-medium">
{toToken}
</span>
</div>
</div>
{/* Swap Details */}
{swapResult && (
<div className="mt-4 space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-500">Rate</span>
<span>1 {fromToken} = {swapResult.effectivePrice.toFixed(4)} {toToken}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Price Impact</span>
<span className={swapResult.priceImpact > 5 ? "text-red-500" : "text-green-500"}>
{swapResult.priceImpact.toFixed(2)}%
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Fee ({amm.state.feePercent}%)</span>
<span>{swapResult.fee.toFixed(4)} {fromToken}</span>
</div>
</div>
)}
{/* Swap Button */}
<button
onClick={handleSwap}
disabled={!swapResult || swapResult.amountOut <= 0}
className="w-full mt-4 py-3 px-4 bg-blue-600 text-white rounded-lg font-medium
hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed
transition-colors"
>
{!amountIn ? "Enter amount" : "Swap"}
</button>
</div>
);
}
Price Curve Chart
TypeScript
// components/PriceCurve.tsx
"use client";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
ReferenceDot,
} from "recharts";
import { ConstantProductAMM } from "@/lib/amm";
interface PriceCurveProps {
amm: ConstantProductAMM;
tokenA: string;
tokenB: string;
}
export function PriceCurve({ amm, tokenA, tokenB }: PriceCurveProps) {
const curveData = amm.generatePriceCurve(100);
const currentState = amm.state;
return (
<div className="bg-white dark:bg-gray-900 rounded-xl p-6 shadow-lg">
<h3 className="text-lg font-semibold mb-4">Price Curve (x * y = k)</h3>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={curveData} margin={{ top: 20, right: 30, left: 20, bottom: 20 }}>
<CartesianGrid strokeDasharray="3 3" opacity={0.3} />
<XAxis
dataKey="reserveA"
label={{ value: tokenA, position: "bottom" }}
tickFormatter={(v) => v.toFixed(0)}
/>
<YAxis
dataKey="reserveB"
label={{ value: tokenB, angle: -90, position: "left" }}
tickFormatter={(v) => v.toFixed(0)}
/>
<Tooltip
formatter={(value: number) => value.toFixed(2)}
labelFormatter={(label) => `${tokenA}: ${Number(label).toFixed(2)}`}
/>
<Line
type="monotone"
dataKey="reserveB"
stroke="#3b82f6"
strokeWidth={2}
dot={false}
/>
{/* Current position marker */}
<ReferenceDot
x={currentState.reserveA}
y={currentState.reserveB}
r={8}
fill="#ef4444"
stroke="#fff"
strokeWidth={2}
/>
</LineChart>
</ResponsiveContainer>
</div>
<div className="mt-4 text-center text-sm text-gray-500">
<span className="inline-flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-red-500" />
Current Position
</span>
</div>
</div>
);
}
Liquidity Interface
TypeScript
// components/LiquidityInterface.tsx
"use client";
import { useState, useMemo } from "react";
import { ConstantProductAMM, LiquidityResult } from "@/lib/amm";
interface LiquidityInterfaceProps {
amm: ConstantProductAMM;
tokenA: string;
tokenB: string;
onAddLiquidity: (result: LiquidityResult) => void;
}
export function LiquidityInterface({
amm,
tokenA,
tokenB,
onAddLiquidity,
}: LiquidityInterfaceProps) {
const [amountA, setAmountA] = useState("");
const [amountB, setAmountB] = useState("");
const liquidityResult = useMemo(() => {
const a = parseFloat(amountA) || 0;
const b = parseFloat(amountB) || 0;
if (a <= 0 && b <= 0) return null;
// Auto-calculate proportional amount
const state = amm.state;
const ratio = state.reserveB / state.reserveA;
if (a > 0 && !amountB) {
return amm.calculateAddLiquidity(a, a * ratio);
}
if (b > 0 && !amountA) {
return amm.calculateAddLiquidity(b / ratio, b);
}
return amm.calculateAddLiquidity(a, b);
}, [amm, amountA, amountB]);
// Auto-fill proportional amount
const handleAmountAChange = (value: string) => {
setAmountA(value);
const a = parseFloat(value);
if (!isNaN(a) && a > 0) {
const state = amm.state;
const ratio = state.reserveB / state.reserveA;
setAmountB((a * ratio).toFixed(4));
}
};
const handleAmountBChange = (value: string) => {
setAmountB(value);
const b = parseFloat(value);
if (!isNaN(b) && b > 0) {
const state = amm.state;
const ratio = state.reserveA / state.reserveB;
setAmountA((b * ratio).toFixed(4));
}
};
const handleAddLiquidity = () => {
if (!liquidityResult) return;
const result = amm.addLiquidity(
parseFloat(amountA) || 0,
parseFloat(amountB) || 0
);
onAddLiquidity(result);
setAmountA("");
setAmountB("");
};
return (
<div className="bg-white dark:bg-gray-900 rounded-xl p-6 shadow-lg">
<h3 className="text-lg font-semibold mb-4">Add Liquidity</h3>
<div className="space-y-4">
<div>
<label className="text-sm text-gray-500 mb-1 block">{tokenA}</label>
<input
type="number"
value={amountA}
onChange={(e) => handleAmountAChange(e.target.value)}
placeholder="0.00"
className="w-full px-4 py-3 bg-gray-50 dark:bg-gray-800 rounded-lg
text-lg font-medium outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="text-center text-gray-400">+</div>
<div>
<label className="text-sm text-gray-500 mb-1 block">{tokenB}</label>
<input
type="number"
value={amountB}
onChange={(e) => handleAmountBChange(e.target.value)}
placeholder="0.00"
className="w-full px-4 py-3 bg-gray-50 dark:bg-gray-800 rounded-lg
text-lg font-medium outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
{liquidityResult && (
<div className="mt-4 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-500">LP Tokens</span>
<span className="font-medium">{liquidityResult.lpTokens.toFixed(4)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Share of Pool</span>
<span className="font-medium">{liquidityResult.shareOfPool.toFixed(2)}%</span>
</div>
</div>
)}
<button
onClick={handleAddLiquidity}
disabled={!liquidityResult}
className="w-full mt-4 py-3 px-4 bg-green-600 text-white rounded-lg font-medium
hover:bg-green-700 disabled:bg-gray-400 disabled:cursor-not-allowed
transition-colors"
>
Add Liquidity
</button>
</div>
);
}
Main Page
TypeScript
// app/page.tsx
"use client";
import { useState, useMemo, useCallback } from "react";
import { ConstantProductAMM, SwapResult, LiquidityResult } from "@/lib/amm";
import { PoolDisplay } from "@/components/PoolDisplay";
import { SwapInterface } from "@/components/SwapInterface";
import { PriceCurve } from "@/components/PriceCurve";
import { LiquidityInterface } from "@/components/LiquidityInterface";
const TOKEN_A = "SOL";
const TOKEN_B = "USDC";
const INITIAL_RESERVE_A = 1000; // 1000 SOL
const INITIAL_RESERVE_B = 20000; // 20000 USDC (price = $20/SOL)
export default function AMMSimulator() {
const [amm, setAmm] = useState(() =>
new ConstantProductAMM(INITIAL_RESERVE_A, INITIAL_RESERVE_B, 0.3)
);
const [history, setHistory] = useState<string[]>([]);
const poolState = amm.state;
const handleSwap = useCallback((result: SwapResult, direction: "AtoB" | "BtoA") => {
const from = direction === "AtoB" ? TOKEN_A : TOKEN_B;
const to = direction === "AtoB" ? TOKEN_B : TOKEN_A;
setHistory(prev => [
`Swapped ${result.amountOut.toFixed(4)} ${from} → ${to} (impact: ${result.priceImpact.toFixed(2)}%)`,
...prev.slice(0, 9),
]);
// Force re-render
setAmm(new ConstantProductAMM(
amm.state.reserveA,
amm.state.reserveB,
amm.state.feePercent
));
}, [amm]);
const handleAddLiquidity = useCallback((result: LiquidityResult) => {
setHistory(prev => [
`Added liquidity: ${result.actualAmountA.toFixed(2)} ${TOKEN_A} + ${result.actualAmountB.toFixed(2)} ${TOKEN_B} → ${result.lpTokens.toFixed(4)} LP`,
...prev.slice(0, 9),
]);
setAmm(new ConstantProductAMM(
amm.state.reserveA,
amm.state.reserveB,
amm.state.feePercent
));
}, [amm]);
const handleReset = () => {
setAmm(new ConstantProductAMM(INITIAL_RESERVE_A, INITIAL_RESERVE_B, 0.3));
setHistory([]);
};
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
<header className="border-b dark:border-gray-800 bg-white dark:bg-gray-900">
<div className="max-w-6xl mx-auto px-4 py-4 flex items-center justify-between">
<h1 className="text-xl font-bold">📊 AMM Simulator</h1>
<button
onClick={handleReset}
className="px-4 py-2 text-sm bg-gray-100 dark:bg-gray-800 rounded-lg
hover:bg-gray-200 dark:hover:bg-gray-700"
>
Reset Pool
</button>
</div>
</header>
<main className="max-w-6xl mx-auto px-4 py-8">
{/* Pool Display */}
<PoolDisplay
reserveA={poolState.reserveA}
reserveB={poolState.reserveB}
tokenA={TOKEN_A}
tokenB={TOKEN_B}
constantK={amm.constantK}
spotPrice={amm.spotPrice}
/>
{/* Main Grid */}
<div className="grid lg:grid-cols-2 gap-6 mt-8">
<SwapInterface
amm={amm}
tokenA={TOKEN_A}
tokenB={TOKEN_B}
onSwap={handleSwap}
/>
<LiquidityInterface
amm={amm}
tokenA={TOKEN_A}
tokenB={TOKEN_B}
onAddLiquidity={handleAddLiquidity}
/>
</div>
{/* Price Curve */}
<div className="mt-8">
<PriceCurve amm={amm} tokenA={TOKEN_A} tokenB={TOKEN_B} />
</div>
{/* History */}
{history.length > 0 && (
<div className="mt-8 bg-white dark:bg-gray-900 rounded-xl p-6 shadow-lg">
<h3 className="text-lg font-semibold mb-4">Transaction History</h3>
<div className="space-y-2 text-sm">
{history.map((item, i) => (
<div key={i} className="py-2 border-b dark:border-gray-800 last:border-0">
{item}
</div>
))}
</div>
</div>
)}
{/* Educational Notes */}
<div className="mt-8 bg-blue-50 dark:bg-blue-900/20 rounded-xl p-6">
<h3 className="text-lg font-semibold mb-4">💡 How AMMs Work</h3>
<div className="grid md:grid-cols-2 gap-6 text-sm">
<div>
<h4 className="font-medium mb-2">Constant Product Formula</h4>
<p className="text-gray-600 dark:text-gray-400">
x × y = k — The product of reserves stays constant.
When you buy token A, you add token B to the pool,
maintaining the invariant.
</p>
</div>
<div>
<h4 className="font-medium mb-2">Price Impact</h4>
<p className="text-gray-600 dark:text-gray-400">
Large trades move the price significantly because
they change the ratio of reserves. This is called
"slippage" or price impact.
</p>
</div>
</div>
</div>
</main>
</div>
);
}
What You'll Learn
| Concept | Implementation |
|---|---|
| Constant Product Formula | x * y = k invariant |
| Swap Calculation | Output = y * Δx / (x + Δx) |
| Price Impact | Difference between spot and effective price |
| LP Tokens | Proportional share of pool |
| Visualization | Interactive price curve chart |
Mini Projects Complete! Next: Fullstack dApp - Build a complete decentralized application.