שילוב Aave, Uniswap ו-Curve בלוח מחוונים אחד ל-DeFi
תארו לעצמכם: יש לכם נכסים בעשרות פרוטוקולי DeFi. כדי לבדוק את התשואה הכוללת, אתם פותחים שלושה ממשקים שונים, ממירים ידנית פוזיציות לדולרים, ועוקבים אחר שינויי APY. זה שעות כל שבוע. אנחנו בונים לוח מחוונים מאוחד: כל הפוזיציות, ערך בדולרים, רווח והפסד, והיסטוריה על מסך אחד, מתעדכן בזמן אמת. למהנדסים שלנו יש ניסיון של 10+ שנים בפיתוח בלוקצ'יין והם מוסמכים ב-Ethereum.
האתגר אינו הווידג'טים— אלא האינטגרציה: לכל פרוטוקול יש API משלו, פורמטים של נתונים ורשתות. בנוסף, יש עדכוני מחירים. אנחנו פותרים את זה ומספקים לוח מחוונים מוכן לשימוש.
אילו פרוטוקולים אנחנו משלבים
| פרוטוקול | סוג נתונים | שיטת שליפה | תדירות עדכון |
|---|---|---|---|
| Aave v3 | הפקדות, הלוואות, APR | Subgraph | 30 שניות |
| Uniswap v3 | פוזיציות LP, עמלות | Subgraph | 30 שניות |
| Curve | סטייקינג, אסימוני LP | Curve API | דקה אחת |
| CoinGecko | מחירי אסימונים | REST API | דקה אחת (מטמון) |
| Ethereum | יתרות, אסימונים | Multicall באצווה RPC | 12 שניות (בלוק אחד) |
אינטגרציית פרוטוקולים ואיסוף נתונים
למה Subgraph מהיר יותר משאילתות RPC ישירות?
Subgraph מ-The Graph מאנדקס אירועי חוזה—שאילתה אחת מחליפה אלפי קריאות RPC. עבור Aave v3, אנחנו מרכיבים שאילתת GraphQL, שולפים // lib/protocols/aave.ts — полный листинг const AAVE_SUBGRAPH = 'https://api.thegraph.com/subgraphs/name/aave/protocol-v3'; interface AavePosition { reserve: { symbol: string; underlyingAsset: string; decimals: number }; currentATokenBalance: string; currentVariableDebt: string; currentStableDebt: string; reserveLiquidationThreshold: string; } export async function getAavePositions(userAddress: string): Promise<AavePosition[]> { const query = `{ userReserves( where: { user: "${userAddress.toLowerCase()}", currentATokenBalance_gt: "0" } ) { reserve { symbol underlyingAsset decimals liquidityRate } currentATokenBalance currentVariableDebt currentStableDebt } }`; const res = await fetch(AAVE_SUBGRAPH, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query }), next: { revalidate: 30 }, }); const { data } = await res.json(); return data.userReserves; } , מנתחים אותם, וממפים לממשק // lib/protocols/uniswap-v3.ts const UNISWAP_SUBGRAPH = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'; export async function getUniswapPositions(ownerAddress: string) { const query = `{ positions( where: { owner: "${ownerAddress.toLowerCase()}", liquidity_gt: "0" } orderBy: id orderDirection: desc first: 20 ) { id liquidity token0 { symbol decimals } token1 { symbol decimals } pool { feeTier sqrtPrice tick token0Price token1Price } depositedToken0 depositedToken1 collectedFeesToken0 collectedFeesToken1 tickLower { tickIdx } tickUpper { tickIdx } } }`; const res = await fetch(UNISWAP_SUBGRAPH, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query }), }); const { data } = await res.json(); return data.positions; } . שאילתות Subgraph מהירות פי 10 לעיבוד אצווה.
// lib/protocols/aave.ts — полный листинг
const AAVE_SUBGRAPH = 'https://api.thegraph.com/subgraphs/name/aave/protocol-v3';
interface AavePosition {
reserve: {
symbol: string;
underlyingAsset: string;
decimals: number;
};
currentATokenBalance: string;
currentVariableDebt: string;
currentStableDebt: string;
reserveLiquidationThreshold: string;
}
export async function getAavePositions(userAddress: string): Promise<AavePosition[]> {
const query = `{
userReserves(
where: {
user: "${userAddress.toLowerCase()}",
currentATokenBalance_gt: "0"
}
) {
reserve {
symbol
underlyingAsset
decimals
liquidityRate
}
currentATokenBalance
currentVariableDebt
currentStableDebt
}
}`;
const res = await fetch(AAVE_SUBGRAPH, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query }),
next: { revalidate: 30 },
});
const { data } = await res.json();
return data.userReserves;
} פוזיציות LP של Uniswap v3 — NFTs ו-Subgraph
פוזיציות Uniswap v3 הן אסימוני ERC-721 ב-NonfungiblePositionManager. דרך ה-Subgraph, אנחנו שולפים פרטים: אסימונים, טווחי tick, עמלות שנאספו. זה מאפשר חישוב ערך נוכחי ורווח לא ממומש.
// lib/protocols/uniswap-v3.ts
const UNISWAP_SUBGRAPH = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3';
export async function getUniswapPositions(ownerAddress: string) {
const query = `{
positions(
where: { owner: "${ownerAddress.toLowerCase()}", liquidity_gt: "0" }
orderBy: id
orderDirection: desc
first: 20
) {
id
liquidity
token0 { symbol decimals }
token1 { symbol decimals }
pool { feeTier sqrtPrice tick token0Price token1Price }
depositedToken0
depositedToken1
collectedFeesToken0
collectedFeesToken1
tickLower { tickIdx }
tickUpper { tickIdx }
}
}`;
const res = await fetch(UNISWAP_SUBGRAPH, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
});
const { data } = await res.json();
return data.positions;
} איך לאגד נתונים בזמן אמת?
אנחנו משתמשים ב-hook usePortfolio הבנוי על TanStack Query + Wagmi. הוא טוען פוזיציות מ-Aave ו-Uniswap במקביל, מביא מחירים, וממזג לאובייקט אחד. // hooks/usePortfolio.ts import { useQuery } from '@tanstack/react-query'; import { useAccount } from 'wagmi'; import { getAavePositions } from '@/lib/protocols/aave'; import { getUniswapPositions } from '@/lib/protocols/uniswap-v3'; import { getTokenPrices } from '@/lib/prices'; export function usePortfolio() { const { address } = useAccount(); return useQuery({ queryKey: ['portfolio', address], queryFn: async () => { if (!address) return null; const [aavePositions, uniswapPositions] = await Promise.all([ getAavePositions(address), getUniswapPositions(address), ]); const tokenIds = [ ...new Set([ ...aavePositions.map(p => p.reserve.symbol.toLowerCase()), 'ethereum', ]), ]; const prices = await getTokenPrices(tokenIds); const aaveUSD = aavePositions.reduce((sum, pos) => { const balance = Number(pos.currentATokenBalance) / 10 ** pos.reserve.decimals; const price = prices[pos.reserve.symbol.toLowerCase()] ?? 0; return sum + balance * price; }, 0); return { aavePositions, uniswapPositions, aaveUSD, prices, }; }, enabled: !!address, refetchInterval: 60_000, }); } של 60 שניות מבטיח רעננות, בעוד מטמון בצד הלקוח מספק תגובה מיידית בעת מעבר בין טאבים.
// hooks/usePortfolio.ts
import { useQuery } from '@tanstack/react-query';
import { useAccount } from 'wagmi';
import { getAavePositions } from '@/lib/protocols/aave';
import { getUniswapPositions } from '@/lib/protocols/uniswap-v3';
import { getTokenPrices } from '@/lib/prices';
export function usePortfolio() {
const { address } = useAccount();
return useQuery({
queryKey: ['portfolio', address],
queryFn: async () => {
if (!address) return null;
const [aavePositions, uniswapPositions] = await Promise.all([
getAavePositions(address),
getUniswapPositions(address),
]);
const tokenIds = [
...new Set([
...aavePositions.map(p => p.reserve.symbol.toLowerCase()),
'ethereum',
]),
];
const prices = await getTokenPrices(tokenIds);
const aaveUSD = aavePositions.reduce((sum, pos) => {
const balance = Number(pos.currentATokenBalance) / 10 ** pos.reserve.decimals;
const price = prices[pos.reserve.symbol.toLowerCase()] ?? 0;
return sum + balance * price;
}, 0);
return {
aavePositions,
uniswapPositions,
aaveUSD,
prices,
};
},
enabled: !!address,
refetchInterval: 60_000,
});
}
איך מטמון נתונים חוסך כסף ומשאבים?
ללא מטמון, כל רינדור מפעיל קריאת API חדשה. יישמנו מטמון מחירים עם TTL של 60 שניות, מה שמפחית את העומס על CoinGecko פי 20. חיסכון במגבלות API מגיע ל-95%, שהם כ-200 דולר בחודש בשימוש כבד. מטמון דומה ל-Subgraph: נתונים נשלפים מחדש לא יותר מפעם בכל 30 שניות.
// lib/prices.ts — кэш цен с TTL
const COINGECKO_BASE = 'https://api.coingecko.com/api/v3';
const priceCache = new Map<string, { price: number; ts: number }>();
const CACHE_TTL = 60_000;
export async function getTokenPrices(
tokenIds: string[],
vsCurrency = 'usd',
): Promise<Record<string, number>> {
const stale = tokenIds.filter(id => {
const cached = priceCache.get(id);
return !cached || Date.now() - cached.ts > CACHE_TTL;
});
if (stale.length > 0) {
const res = await fetch(
`${COINGECKO_BASE}/simple/price?ids=${stale.join(',')}&vs_currencies=${vsCurrency}`,
);
const data = await res.json();
for (const [id, price] of Object.entries(data)) {
priceCache.set(id, {
price: (price as Record<string, number>)[vsCurrency],
ts: Date.now(),
});
}
}
return Object.fromEntries(
tokenIds.map(id => [id, priceCache.get(id)?.price ?? 0]),
);
} רכיבי לוח המחוונים
// components/NetWorthCard.tsx
export function NetWorthCard() {
const { data, isLoading } = usePortfolio();
if (isLoading) return <Skeleton className="h-32" />;
const total = (data?.aaveUSD ?? 0) + (data?.uniswapUSD ?? 0);
return (
<div className="rounded-2xl border border-white/10 bg-gradient-to-br from-neutral-900 to-neutral-800 p-6">
<p className="text-sm text-neutral-400">Чистая стоимость</p>
<p className="mt-1 text-4xl font-bold">
${total.toLocaleString('en-US', { maximumFractionDigits: 2 })}
</p>
<div className="mt-4 flex gap-6 text-sm">
<Metric label="Aave" value={data?.aaveUSD} />
<Metric label="Uniswap LP" value={data?.uniswapUSD} />
</div>
</div>
);
}עדכונים בזמן אמת: מחירים מתעדכנים כל דקה דרך react-query // lib/prices.ts — кэш цен с TTL const COINGECKO_BASE = 'https://api.coingecko.com/api/v3'; const priceCache = new Map<string, { price: number; ts: number }>(); const CACHE_TTL = 60_000; export async function getTokenPrices( tokenIds: string[], vsCurrency = 'usd', ): Promise<Record<string, number>> { const stale = tokenIds.filter(id => { const cached = priceCache.get(id); return !cached || Date.now() - cached.ts > CACHE_TTL; }); if (stale.length > 0) { const res = await fetch( `${COINGECKO_BASE}/simple/price?ids=${stale.join(',')}&vs_currencies=${vsCurrency}`, ); const data = await res.json(); for (const [id, price] of Object.entries(data)) { priceCache.set(id, { price: (price as Record<string, number>)[vsCurrency], ts: Date.now() }); } } return Object.fromEntries( tokenIds.map(id => [id, priceCache.get(id)?.price ?? 0]), ); } , נתוני on-chain כל 12 שניות (בלוק Ethereum אחד), נתוני Subgraph כל 30 שניות (עיכוב אינדוקס ~15 שניות).
תהליך הפיתוח
| שלב | משך | תוצאה |
|---|---|---|
| ניתוח ובריף | יום אחד | רשימת פרוטוקולים, מפרט |
| אינטגרציית Subgraph | 2–3 ימים | שליפת נתונים לכל פרוטוקול |
| פיתוח ממשק משתמש | 2–4 ימים | רכיבים: NetWorth, Positions, History |
| מטמון וזמן אמת | יום אחד | הגדרת react-query, מטמון מחירים |
| בדיקות ופריסה | 1–2 ימים | לוח מחוונים עובד על Vercel או השרת שלכם |
מלכודות נפוצות ואיך להימנע מהן
- שאילתת N+1: שליפת נתונים לכל בריכה עם קריאת RPC נפרדת. פתרון: Multicall או Subgraph עם שאילתות אצווה.
- אי-התאמה ב-Hydration ב-Next.js: נתונים שמתקבלים בשרת לא תואמים לנתוני הלקוח בגלל זמני בקשה שונים. תיקון: דחיית שאילתות ללקוח באמצעות Wagmi.
-
נתונים מיושנים: ללא מטמון, משתמשים רואים מחירים לא מעודכנים. השתמשו ב-TTL של 60 שניות וב-
// components/NetWorthCard.tsx export function NetWorthCard() { const { data, isLoading } = usePortfolio(); if (isLoading) return <Skeleton className="h-32" />; const total = (data?.aaveUSD ?? 0) + (data?.uniswapUSD ?? 0); return ( <div className="rounded-2xl border border-white/10 bg-gradient-to-br from-neutral-900 to-neutral-800 p-6"> <p className="text-sm text-neutral-400">Чистая стоимость</p> <p className="mt-1 text-4xl font-bold"> ${total.toLocaleString('en-US', { maximumFractionDigits: 2 })} </p> <div className="mt-4 flex gap-6 text-sm"> <Metric label="Aave" value={data?.aaveUSD} /> <Metric label="Uniswap LP" value={data?.uniswapUSD} /> </div> </div> ); }כדי לשמור על רעננות הנתונים.
אחד הלקוחות שלנו צמצם את זמן הניטור הידני מ-10 שעות בשבוע ל-15 דקות לאחר יישום לוח המחוונים—חיסכון של מעל 2,000 דולר בשנה בעלויות עבודה.
מה כלול במשלוח
אנחנו מוסרים פתרון מלא ומוכן:
- אינטגרציה של פרוטוקולים נבחרים (עד 5)
- ארכיטקטורה הבנויה על Next.js + TypeScript + Wagmi + TanStack Query
- מטמון למחירים ולנתוני Subgraph
- ממשק משתמש רספונסיבי באמצעות Tailwind CSS
- תיעוד לפריסה ולהוספת פרוטוקולים חדשים
- תמיכה חינמית לחודש אחד לאחר המסירה
לוחות זמנים
- MVP עם פרוטוקול אחד (Aave) — 3-4 ימים
- לוח מחוונים רב-פרוטוקולי (3-4 פרוטוקולים + גרפים + היסטוריה) — 1.5-2 שבועות
- לוחות זמנים מדויקים מוערכים לאחר הבריף
הזמינו לוח מחוונים לתיק ההשקעות שלכם. קבלו ייעוץ: צרו קשר, ואנחנו נכין הצעה מותאמת לפרוטוקולים ולדרישות שלכם. למהנדסים שלנו יש ניסיון של 5+ שנים ב-DeFi והם מבטיחים פעילות יציבה של לוח המחוונים.







