אנו בונים לוחות מחוונים (דשבורדים) מקיפים לניהול תיקי DeFi. דמיינו משתמש שמחזיק USDC ב-Aave, LP של ETH-USDC ב-Uniswap V3, wstETH ב-Lido, ופוזיציה פתוחה בחוזה עתידי (perpetual) ב-GMX. ארבעה פרוטוקולים שונים, ארבע דרכים שונות לייצוג פוזיציות, וארבעה APIs או subgraphs שונים. תפקידו של לוח המחוונים הוא לאחד את הכל למסך אחד עם מספרי רווח והפסד (P&L) אמיתיים. הניסיון שלנו מראה ש-80% מהמאמץ מושקע בשכבת הנתונים: נרמול נתונים ממקורות שונים וחישוב נכון של יתרות מיושנות לעומת יתרות חיות. אנו מבטיחים דיוק ברמת בלוק וזמן השהיה מינימלי. צרו קשר כדי להעריך את מחסן הטכנולוגיות של הפרויקט שלכם.
כיצד מחושבים רווח והפסד (P&L) והפסד בלתי-קבוע (Impermanent Loss)?
החלק הקשה ביותר הוא חישוב נכון של רווח והפסד לא ממומש (unrealized P&L) עבור פוזיציות LP. עבור Uniswap V3, פוזיציה היא NFT עם tickLower, tickUpper, ו-liquidity ספציפיים. הכמויות הנוכחיות של token0 ו-token1 תלויות ב-sqrtPriceX96 הנוכחי של הבריכה. הנוסחה אינה טריוויאלית:
function getAmountsFromLiquidity(
sqrtPriceX96: bigint,
sqrtRatioAX96: bigint,
sqrtRatioBX96: bigint,
liquidity: bigint
): [bigint, bigint] {
if (sqrtPriceX96 <= sqrtRatioAX96) {
const amount0 =
(liquidity * (sqrtRatioBX96 - sqrtRatioAX96) * Q96) /
(sqrtRatioBX96 * sqrtRatioAX96)
return [amount0, 0n]
} else if (sqrtPriceX96 < sqrtRatioBX96) {
const amount0 =
(liquidity * (sqrtRatioBX96 - sqrtPriceX96) * Q96) /
(sqrtRatioBX96 * sqrtPriceX96)
const amount1 = (liquidity * (sqrtPriceX96 - sqrtRatioAX96)) / Q96
return [amount0, amount1]
} else {
const amount1 = (liquidity * (sqrtRatioBX96 - sqrtRatioAX96)) / Q96
return [0n, amount1]
}
}הפסד בלתי-קבוע מחושב כהפרש בין הערך הנוכחי של הפוזיציה לבין הערך שהיה אם אותם נכסים היו פשוט מוחזקים מאז הכניסה. לוח המחוונים חייב לאחסן את מחיר הכניסה והכמויות הראשוניות בעת פתיחת הפוזיציה.
באילו מקורות נתונים אנו משתמשים?
אנו משלבים קריאות ישירות בשרשרת (on-chain) ואינדקסרים. הדרך המדויקת ביותר לקבל יתרה היא function getAmountsFromLiquidity( sqrtPriceX96: bigint, sqrtRatioAX96: bigint, sqrtRatioBX96: bigint, liquidity: bigint ): [bigint, bigint] { if (sqrtPriceX96 <= sqrtRatioAX96) { const amount0 = (liquidity * (sqrtRatioBX96 - sqrtRatioAX96) * Q96) / (sqrtRatioBX96 * sqrtRatioAX96) return [amount0, 0n] } else if (sqrtPriceX96 < sqrtRatioBX96) { const amount0 = (liquidity * (sqrtRatioBX96 - sqrtPriceX96) * Q96) / (sqrtRatioBX96 * sqrtPriceX96) const amount1 = (liquidity * (sqrtPriceX96 - sqrtRatioAX96)) / Q96 return [amount0, amount1] } else { const amount1 = (liquidity * (sqrtRatioBX96 - sqrtRatioAX96)) / Q96 return [0n, amount1] } } ישירה לחוזה. עבור יתרת טוקן — eth_call. עבור פוזיציית Aave — balanceOf(). זה תמיד עדכני אבל איטי: כל פרוטוקול דורש קריאות נפרדות, וזמן ההשהיה גדל ליניארית עם עשרות פרוטוקולים. הפתרון הוא Multicall3 (חוזה 0xC...A11, פרוס על כל רשתות ה-EVM המרכזיות): איגוד של 50+ קריאות בעסקה אחת. זמן התגובה הוא כמו קריאת RPC אחת במקום 50. השוואה: Multicall3 מהיר פי 20 מקריאות רציפות.
import { multicall } from 'viem'
const results = await multicall(client, {
contracts: [
{
address: AAVE_POOL,
abi: aavePoolAbi,
functionName: 'getUserAccountData',
args: [userAddress]
},
{
address: USDC_TOKEN,
abi: erc20Abi,
functionName: 'balanceOf',
args: [userAddress]
},
{
address: UNISWAP_POSITION_MANAGER,
abi: nftAbi,
functionName: 'balanceOf',
args: [userAddress]
},
]
})עבור נתונים היסטוריים (היסטוריית עסקאות, רווח והפסד לאורך זמן) קריאות ישירות לא עובדות — אנו זקוקים לאינדקסרים.
אנו משתמשים ב-The Graph עבור נתונים היסטוריים. ל-Uniswap, Aave, Compound, Curve ו-Balancer יש כולם subgraphs רשמיים ברשת The Graph. Subgraph מספק ממשק GraphQL לשאילתת אירועים היסטוריים: הפקדות, משיכות, החלפות ופירוקים (liquidations).
query UserPositions($user: String!) {
aaveV3_deposits(where: { user: $user }, orderBy: timestamp, orderDirection: desc) {
amount
reserve {
symbol
decimals
priceInUSD
}
timestamp
}
aaveV3_borrows(where: { user: $user }) {
amount
reserve {
symbol
}
currentVariableBorrowRate
}
}בעיה: לגרסאות פרוטוקול שונות יש subgraphs שונים. Aave V2 ב-Ethereum, Aave V3 ב-Polygon, Aave V3 ב-Arbitrum — שלושה subgraphs שונים עם סכמות שונות. נרמול הוא משימת ההנדסה המרכזית של לוח המחוונים. אנו משתמשים בשכבת הפשטה אחידה שממפה את כל הסכמות למודל נתונים יחיד.
Alchemy ו-Moralis כ-API-over-RPC. Alchemy API מספקת מתודות מוכנות: getUserAccountData() מחזירה את כל יתרות ה-ERC-20 של כתובת ללא צורך במעבר על חוזים. import { multicall } from 'viem' const results = await multicall(client, { contracts: [ { address: AAVE_POOL, abi: aavePoolAbi, functionName: 'getUserAccountData', args: [userAddress] }, { address: USDC_TOKEN, abi: erc20Abi, functionName: 'balanceOf', args: [userAddress] }, { address: UNISWAP_POSITION_MANAGER, abi: nftAbi, functionName: 'balanceOf', args: [userAddress] }, ] }) מספקת היסטוריית העברות. זה מפשט משמעותית את היישום הראשוני, אבל עולה כסף בעומס גבוה. Moralis מצטיינת בנוסף באיגוד פוזיציות NFT ופוזיציות פרוטוקול DeFi דרך ה-DeFi API שלה — בתשלום, אבל חוסכת חודשים של פיתוח שכבת נתונים מותאמת אישית. עבור MVP, Alchemy + The Graph עבור פרוטוקולים מרכזיים הוא מוצדק. עבור ייצור עם עשרות אלפי משתמשים, אנו ממליצים על אינדקסר מותאם אישית.
| מקור | רעננות | עלות | מורכבות אינטגרציה |
|---|---|---|---|
| Multicall3 | חי (ברמת בלוק) | חינם (גז RPC) | בינונית |
| The Graph | פיגור של ~דקה | חינם (מוגבל בקצב) | גבוהה (סכמות שונות) |
| Alchemy API | חי | תשלום לפי בקשה | נמוכה |
| Moralis | חי | מנוי | נמוכה |
איגוד רב-שרשרתי (Multi-Chain Aggregation)
משתמש טיפוסי פעיל ב-Ethereum mainnet, Arbitrum, Polygon ו-Base. לוח המחוונים חייב להציג את התיק הכולל על פני כל השרשרות. הגישה שלנו: בקשות מקבילות ל-RPC של כל שרשרת דרך query UserPositions($user: String!) { aaveV3_deposits(where: { user: $user }, orderBy: timestamp, orderDirection: desc) { amount reserve { symbol, decimals, priceInUSD } timestamp } aaveV3_borrows(where: { user: $user }) { amount reserve { symbol } currentVariableBorrowRate } } , נרמול יתרות לדולרים עם אורקל מחירים אחיד. אנו משתמשים ב-Coingecko API או ב-DefiLlama Price API כדי לקבל מחירים עדכניים לפי כתובת טוקן ומזהה שרשרת. בעיית הזהות בין-שרשרתית: כתובת המשתמש זהה בכל שרשראות ה-EVM (ECDSA), אבל ארנק חוזה חכם (Safe, Argent) עשוי להיות בעל כתובות שונות בשרשראות שונות אם הפריסה לא סונכרנה. אנו תומכים במצב רב-כתובות: המשתמש יכול להוסיף מספר כתובות לפרופיל אחד.
כיצד אנו מבטיחים ביצועים
צד שרת: Node.js + TypeScript עם viem ל-RPC. Redis לשמירת יתרות במטמון (TTL של 30 שניות לנתונים חיים, 5 דקות להיסטוריים). PostgreSQL לאחסון תמונות מצב היסטוריות של התיק (לבניית עקומות הון).
צד לקוח: React + wagmi v2 לחיבור ארנק, Recharts או TradingView Lightweight Charts לגרפים, Tanstack Query לאחזור נתונים עם רענון אוטומטי כל 30 שניות.
WebSocket לעדכונים בזמן אמת: הרשמה ל-getTokenBalances() מפעילה עדכוני יתרות בכל בלוק חדש — מספקת חיוניות ללא סבבי שאילתות מיותרים.
דוגמה לתצורת WebSocket
const transport = http('https://mainnet.infura.io/v3/YOUR_KEY') const client = createPublicClient({ transport }) מה כלול
- תיעוד שכבת נתונים (סכמות נתונים, תיאור נרמול).
- גישה מלאה למאגר קוד לוח המחוונים.
- שעתיים של הדרכה מקוונת לצוות.
- שבועיים של תמיכה חינמית לאחר הפריסה.
- אפשרות ל-SLA לתחזוקה שוטפת.
רקורד שלנו: 5 שנים בפיתוח בלוקצ'יין, יותר מ-20 פרויקטי DeFi מוצלחים. אנו מבטיחים שלוח המחוונים יפעל ללא השבתות ובדיוק ברמת בלוק.
תהליך
- ניתוח (1–2 ימים). רשימת פרוטוקולים ושרשרות יעד, תעדוף לפי מקרי שימוש פופולריים של הקהל.
- שכבת נתונים (5–7 ימים). בניית מאגד Multicall, אינטגרציה עם The Graph לפרוטוקולים מרכזיים, נרמול לסכמת פוזיציות אחידה.
- Backend API (3–5 ימים). מתן REST/GraphQL API לצד לקוח, שמירה במטמון, היסטוריית תיק.
- צד לקוח (5–7 ימים). יישום חיבור ארנק, תצוגת יתרה כוללת, פירוט לפי פרוטוקול, גרפים.
הערכות לוחות זמנים
| שלב | לוח זמנים |
|---|---|
| MVP (5–7 פרוטוקולים, 2–3 שרשרות) | 2–3 שבועות |
| לוח מחוונים מלא (היסטוריה, IL, התראות, מובייל) | 6–8 שבועות |
קבלו ייעוץ לפרויקט שלכם — נעריך את המורכבות ואת לוחות הזמנים בחינם.







