מאגרגטור 1inch הדגים רעיון פשוט: אם אתם מחפשים את המחיר הטוב ביותר רק ב-DEX אחד, אתם משאירים כסף על השולחן. מאז, מערכות ניתוב הזמנות התפתחו לפתרונות מורכבים עם ניתוב מפוצל, קפיצות מרובות ואלגוריתמים ייעודיים. אנו מתכננים ומיישמים מערכות כאלה במפתח מלא — מאפס או כהרחבה של תשתית קיימת.
מערכת ניתוב הזמנות מותאמת אישית נדרשת כאשר מאגרגטורים סטנדרטיים (1inch, ParaSwap, 0x) לא תומכים ברשת הנדרשת; נדרשת אינטגרציה של פרוטוקולים מותאמים אישית; נדרשת שליטה על מקורות נזילות; או שה-API הקיים איטי מדי עבור בוט מסחר. רקורד שלנו: 5+ שנים בפיתוח בלוקצ'יין ו-30+ פרויקטי DeFi.
כיצד פועלת מערכת ניתוב הזמנות על פני מספר DEXים?
ניתוב הוא בעיית מציאת נתיב בגרף מכוון עם משקלים. הקודקודים הם טוקנים. הקשתות הן מאגרי נזילות — כל מאגר יוצר שתי קשתות מכוונות: A→B ו-B→A עם המחיר בכיוון זה.
כדי למצוא את הנתיב הטוב ביותר עבור amountIn קבוע, המשימה היא למצוא את הנתיב עם המכפלה המקסימלית של שערי החליפין (או באופן שקול, הסכום המינימלי של לוגריתמים שליליים). זהו שינוי של אלגוריתם Bellman-Ford או Dijkstra.
אבל יש ניואנס שמקשה על הבעיה: המחיר במאגר תלוי בנפח. עבור amountIn = 100 USDC, הנתיב הטוב ביותר עשוי להיות מאגר Uniswap V3 עם עמלה של 0.05%. עבור amountIn = 1,000,000 USDC, אותו מאגר נותן החלקה של 3%, בעוד שפיצול על פני מספר מאגרים נותן 0.3%. זה הופך את הבעיה ממציאת נתיב בגרף עם משקלים קבועים לבעיית אופטימיזציה עם משקלים תלויים בנפח.
כיצד ניתוב מפוצל מפחית החלקה בהזמנות גדולות?
עבור הזמנות גדולות, הפתרון האופטימלי אינו נתיב יחיד אלא חלוקת הנפח על פני מספר נתיבים. הגישה המשתמשת בחיפוש בינארי לפיצול אופטימלי לשני נתיבים:
function findOptimalSplit(
routeA: Route,
routeB: Route,
totalAmount: bigint,
steps: number = 20
): { splitA: bigint; splitB: bigint; totalOut: bigint } {
let bestSplit = { splitA: 0n, splitB: totalAmount, totalOut: 0n }
for (let i = 0; i <= steps; i++) {
const fraction = i / steps
const amountA = BigInt(Math.floor(Number(totalAmount) * fraction))
const amountB = totalAmount - amountA
const outA = amountA > 0n ? simulateRoute(routeA, amountA) : 0n
const outB = amountB > 0n ? simulateRoute(routeB, amountB) : 0n
const totalOut = outA + outB
if (totalOut > bestSplit.totalOut) {
bestSplit = { splitA: amountA, splitB: amountB, totalOut }
}
}
return bestSplit
}עבור N נתיבים, הבעיה הופכת לאופטימיזציה N-ממדית — אנו מיישמים ירידת גרדיאנט או Nelder-Mead עם אילוצים (סכום החלקים = 1, כל החלקים ≥ 0).
סימולציית מאגר: דיוק מול מהירות
Uniswap V2: נוסחה מדויקת
function getAmountOutV2(amountIn: bigint, reserveIn: bigint, reserveOut: bigint): bigint {
const amountInWithFee = amountIn * 997n
const numerator = amountInWithFee * reserveOut
const denominator = reserveIn * 1000n + amountInWithFee
return numerator / denominator
}מסמך לבן של Uniswap V2
Uniswap V3: מעבר על טיקים
V3 דורש מעבר על מפת הביטים של הטיקים כדי למצוא את הטיקים הפעילים הקרובים ביותר. סימולציה מלאה מדויקת אך איטית — מספר מילישניות עבור עסקה גדולה עם מעבר דרך טיקים רבים.
להערכה מהירה (במהלך סינון נתיבים), אנו משתמשים בקירוב דרך sqrtPriceX96 הנוכחי ונזילות ללא מעבר על טיקים — מדויק לנפחים קטנים, עם שגיאה לנפחים גדולים. סימולציה מדויקת מתבצעת רק עבור מועמדים סופיים.
Curve StableSwap: נוסחה איטרטיבית
Curve משתמשת באינווריאנט A * n^n * sum(x_i) + D = A * D * n^n + D^(n+1) / (n^n * prod(x_i)). חישוב amountOut הוא איטרטיבי (שיטת ניוטון). עבור JavaScript/TypeScript — חשבון BigInt עם דיוק של 18 עשרוניות.
Balancer WeightedPool
Balancer עם מאגרים משוקללים (לדוגמה, 80/20 BAL/ETH) משתמש באינווריאנט שונה. getAmountOut תלוי במשקלי הטוקנים במאגר — נוסחה מורכבת יותר מאשר ב-V2.
ניתוב On-Chain לעומת Off-Chain
ניתוב יכול להתרחש כולו על השרשרת (חוזה חכם מוצא את הנתיב בתוך העסקה) או מחוץ לשרשרת (חישוב מחוץ לשרשרת, התוצאה מועברת לחוזה).
ניתוב On-Chain: שקיפות מלאה, אין אפשרות למניפולציה על ידי המאגרגטור. בעיה: גז מוגבל, לא ניתן לעבור על כל הנתיבים. משמש למקרים פשוטים (מקסימום 2–3 מאגרים).
ניתוב Off-Chain (גישת 1inch, ParaSwap): חישוב בקצה האחורי, החוזה מקבל נתיב מוכן. החוזה רק מבצע. חסכוני בגז, הנתיב יכול להיות מורכב יותר. סיכון: הקצה האחורי עשוי להחזיר נתיב לא אופטימלי. הגנה באמצעות הגנת החלקה: minAmountOut בעסקה מבטיח מינימום למשתמש.
כיצד בנוי חוזה הנתב?
החוזה חייב לתמוך בנתיבים הטרוגניים: חלק דרך Uniswap V2, חלק דרך V3, חלק דרך Curve.
struct SwapStep {
address pool;
address tokenIn;
address tokenOut;
uint24 fee; // Для V3
uint8 dexType; // 0=V2, 1=V3, 2=Curve, 3=Balancer
bytes extraData; // Дополнительные параметры под тип DEX
}
function multiSwap(
SwapStep[] calldata steps,
uint256 amountIn,
uint256 minAmountOut,
address recipient
) external returns (uint256 amountOut) {
IERC20(steps[0].tokenIn).transferFrom(msg.sender, address(this), amountIn);
uint256 currentAmount = amountIn;
for (uint256 i = 0; i < steps.length; i++) {
currentAmount = _executeStep(steps[i], currentAmount);
}
require(currentAmount >= minAmountOut, "Slippage exceeded");
IERC20(steps[steps.length-1].tokenOut).transfer(recipient, currentAmount);
return currentAmount;
}_executeStep מפנה למימוש ה-DEX הספציפי בהתבסס על dexType. כל מימוש הוא ספרייה נפרדת (תבנית ספריית Solidity) כדי לחסוך בגודל ה-bytecode.
מדוע מטמון מאגרים קריטי למהירות?
לניתוב מהיר ללא קריאות RPC לכל בקשה, אנו זקוקים למטמון של המצב הנוכחי של המאגרים: מנויי WebSocket על אירועי Sync (מאגרי V2) ואירועי Swap (מאגרי V3) דרך eth_subscribe("logs"). בכל אירוע, אנו מעדכנים את ה-reserves/sqrtPrice בזיכרון.
עבור 500–1000 מאגרים פעילים, זה בערך 50–100 אירועים/בלוק ברשת Ethereum הראשית. עיבוד באמצעות ארכיטקטורה מונעת אירועים (Node.js EventEmitter או ערוץ tokio של Rust) עם זמן השהיית עדכון של ≤1ms.
התחלה קרה: בעת הפעלת השירות, עלינו לטעון את המצב הנוכחי של כל המאגרים באמצעות multicall. עבור 1000 מאגרים — 5–10 עסקאות multicall (עד 200 קריאות כל אחת), לוקח 1–3 שניות.
השוואת גישות ארכיטקטוניות
| גישה | מתי היא מתאימה | מורכבות | זמן השהיה |
|---|---|---|---|
| קפיצות מרובות פשוטות | 3–5 רשתות, 5 ה-DEXים המובילים | נמוכה | 200–500ms |
| ניתוב מפוצל | הזמנות גדולות ($50K+) | בינונית | 500ms–1s |
| עם מטמון מאגרים | בוט מסחר, < 50ms | גבוהה | 10–50ms |
| נתב On-Chain | שקיפות מקסימלית | בינונית | בלוק אחד |
זמן פיתוח משוער
| שלב | זמן |
|---|---|
| אנליטיקה (רשימת DEXים, דרישות זמן השהיה) | 1–2 ימים |
| פיתוח מנוע ניתוב (גרף, אלגוריתם, סימולציה) | 5–7 ימים |
| חוזה נתב (רב-שלבי, בדיקות fork) | 3–5 ימים |
| מטמון מאגרים (WebSocket + בזיכרון) | 3–5 ימים |
| אינטגרציה, בדיקות, תיעוד | 2–3 ימים |
תהליך פיתוח שלב אחר שלב
- ניתוח גרף מאגרים ונזילות — בניית מפת טוקנים ומאגרים עבור הרשתות שלכם.
- פיתוח אלגוריתם מציאת נתיב — יישום Dijkstra מותאם המתייחס לתלות בנפח.
- אינטגרציית סימולציה — כתיבת סימולטורים עבור Uniswap V2/V3, Curve, Balancer.
- בדיקות fork של Mainnet — הרצת הזמנות בגדלים שונים, השוואה מול קו בסיס.
- פריסה וניטור — השקה ב-testnet ו-mainnet, הגדרת התראות.
מה כלול בעבודה
- תיעוד ארכיטקטורה: תיאור גרף מאגרים, תוכנית מטמון, מפרט חוזה.
- מנוע ניתוב: יישום מציאת נתיב וניתוב מפוצל עם תמיכה ב-V2/V3/Curve/Balancer.
- חוזה נתב: ביצוע רב-שלבי עם הגנת החלקה.
- מטמון מאגרים (אופציונלי): מנויי WebSocket + אחסון בזיכרון.
- בדיקות: בדיקות fork של Foundry, פיזינג Echidna.
- אינטגרציה: פריסה ב-testnet ו-mainnet, הגדרת ניטור.
- הכשרת צוות: תיעוד פנימי, סקירת קוד.
הזמינו ייעוץ — נשלח הצעה טכנית ומסחרית תוך יום אחד לאחר הבריף. קבלו ניתוח מפורט של התשתית הנוכחית שלכם והמלצות לאופטימיזציה של ניתוב. צרו קשר כדי לדון בפרויקט שלכם.







