Bundler הוא רכיב התשתית המרכזי של אקוסיסטם ERC-4337. אנו יוצרים bundlers מוכנים לייצור עבור המשימות שלך: מגרסה מרכזית בסיסית ועד לגרסה מותאמת ל-MEV עם mempool מבוסס P2P. הניסיון שלנו: מעל 5 שנים בפיתוח בלוקצ'יין ומעל 50 פריסות של פתרונות ERC-4337. ה-bundler הוא זה שגורם ל-Account Abstraction לעבוד: הוא מקבל UserOperations ממשתמשים, מאמת אותם, אורז אותם, ושולח אותם לרשת דרך EntryPoint.handleOps(). ללא bundler, Account Abstraction לא עובד—אין מנגנון להעברת UserOps לבלוקצ'יין.
פיתוח bundler משלך רלוונטי כאשר: אתה צריך לוגיקת mempool מותאמת אישית, אופטימיזציית MEV עבור UserOps, bundler פרטי עבור יישום ספציפי, או כאשר אתה צריך להבין ולשלוט בכל תשתית ERC-4337. אנו מבטיחים יישום נכון של כללי גישת האחסון—המורכבות המרכזית שמכשילה 70% מה-bundlers הביתיים.
כיצד Bundler מאמת UserOperations
משתמש יוצר UserOperation ושולח אותו ל-bundler דרך שיטת JSON-RPC eth_sendUserOperation. ה-bundler מבצע סדרת בדיקות, שומר את ה-UserOp ב-alt mempool שלו, ומגיש אצוות מעת לעת לרשת.
שלב 1: אימות
ה-bundler קורא ל-initCode. זוהי פונקציית view (חוזרת עם תוצאה דרך שגיאה מותאמת אישית) ש:
- אם
interface ValidationResult { returnInfo: { preOpGas: bigint; prefund: bigint; // сколько ETH account/paymaster депозировал в EntryPoint sigFailed: boolean; validAfter: number; validUntil: number; }; senderInfo: StakeInfo; factoryInfo?: StakeInfo; paymasterInfo?: StakeInfo; }אינו ריק—פורסת את חוזה ה-Account דרך factory. - קוראת ל-
prefund—בודקת חתימה, nonce. - אם צוין Paymaster—קוראת ל-
maxFeePerGas * (verificationGasLimit + callGasLimit). - מחזירה
validateUserOpעם נתוני גז, מידע על staking של paymaster, ומגבלות זמן.
interface ValidationResult {
returnInfo: {
preOpGas: bigint;
prefund: bigint; // сколько ETH account/paymaster депозировал в EntryPoint
sigFailed: boolean;
validAfter: number;
validUntil: number;
};
senderInfo: StakeInfo;
factoryInfo?: StakeInfo;
paymasterInfo?: StakeInfo;
}// Упрощённо: tracer для отслеживания storage access async function traceValidation(userOp: UserOperation): Promise<StorageMap> { const trace = await provider.send('debug_traceCall', [{ to: ENTRY_POINT_ADDRESS, data: entryPoint.interface.encodeFunctionData('simulateValidation', [userOp]) }, 'latest', { tracer: bundlerCollectorTracer, // кастомный JS tracer tracerConfig: { /* ... */ } }]) return parseStorageAccess(trace) } הוא המפתח. לחשבון או ל-paymaster חייב להיות הפקדה ב-EntryPoint מספיקה לכיסוי bundlerCollectorTracer. ה-bundler בודק זאת לפני הכללה ב-mempool.
מדוע כללי גישת האחסון קריטיים
ERC-4337 מטיל הגבלות מחמירות על איזה אחסון debug_traceCall יכול לקרוא/לכתוב. המטרה היא למנוע מצב שבו UserOp אחד מבטל אחרים (מתקפת griefing).
אסור במהלך אימות:
- קריאת אחסון של חוזים אחרים מלבד החשבון עצמו וישויות קשורות.
- קריאה ל-
maxFeePerGas,class UserOpMempool { private pool: Map<string, MempoolEntry> = new Map() async add(userOp: UserOperation): Promise<string> { const hash = getUserOpHash(userOp) // Репутационная система: ограничение по sender/paymaster/factory this.reputationManager.checkReputation(userOp) this.pool.set(hash, { userOp, prefund: await this.calculatePrefund(userOp), addedAt: Date.now() }) return hash } getBundle(maxGas: bigint): UserOperation[] { // Жадный алгоритм: выбрать UserOps с наибольшим priority fee // с учётом лимита газа и конфликтов по storage return this.selectNonConflicting( [...this.pool.values()] .sort((a, b) => Number(b.userOp.maxPriorityFeePerGas - a.userOp.maxPriorityFeePerGas)), maxGas ) } }(למעט שימוש מוגבל דרךbeneficiary/handleOps). - גישה לאחסון שעלול להשתנות על ידי UserOp אחר באותה אצווה.
דרישה זו מתוארת ב-מפרט ERC-4337. ה-bundler עוקב אחרי משבצות האחסון שנגישו במהלך האימות באמצעות handleOps עם מעקב EVM. זוהי פעולה יקרה—אחד מצווארי הבקבוק העיקריים של bundler.
// Упрощённо: tracer для отслеживания storage access
async function traceValidation(userOp: UserOperation): Promise<StorageMap> {
const trace = await provider.send('debug_traceCall', [
{
to: ENTRY_POINT_ADDRESS,
data: entryPoint.interface.encodeFunctionData('simulateValidation', [userOp])
},
'latest',
{
tracer: bundlerCollectorTracer, // кастомный JS tracer
tracerConfig: { /* ... */ }
}
])
return parseStorageAccess(trace)
}handleOps הוא מעקב JavaScript עבור class ReputationManager { // Для каждого entity отслеживаем: ops включённых vs ops неуспешных updateIncluded(entity: string): void { this.entries[entity].opsSeen++ this.entries[entity].opsIncluded++ } updateFailed(entity: string): void { this.entries[entity].opsIncluded-- // если bundle был reverted } getStatus(entity: string): 'ok' | 'throttled' | 'banned' { const entry = this.entries[entity] if (!entry) return 'ok' const ratio = entry.opsIncluded / Math.max(1, entry.opsSeen) if (ratio < MIN_INCLUSION_RATE_DENOMINATOR) return 'banned' if (entry.opsSeen > THROTTLE_THRESHOLD) return 'throttled' return 'ok' } } של go-ethereum. הוא עוקב אחרי כל אופרטור SLOAD/SSTORE ומקשר אותם לחוזה הקורא. זהו החלק הטכני המאתגר ביותר של bundler.
ניהול ה-Alternative Mempool
UserOp שהתקבל ל-mempool חייב להישאר תקף. ה-bundler עוקב אחר:
- ביטול nonce. אם ה-nonce של חשבון ברשת משתנה (UserOp אחר עבר), ה-UserOp הממתין עם ה-nonce הישן מוסר.
- חוסר הפקדה. אם יתרת ההפקדה ב-EntryPoint יורדת (UserOp אחר בחסות אותו Paymaster עבר), ה-bundler חייב לחשב מחדש אם היא מכסה את כל ה-UserOps הממתינים של אותו Paymaster.
- שינויי מחיר גז. UserOp עם
import { createLibp2p } from 'libp2p' import { gossipsub } from '@chainsafe/libp2p-gossipsub' const libp2p = await createLibp2p({ /* ... transport, identify, etc */ services: { pubsub: gossipsub({ allowPublishToZeroPeers: true, msgIdFn: (msg) => computeUserOpHash(msg.data) }) } }) libp2p.services.pubsub.subscribe(userOpsTopic) libp2p.services.pubsub.addEventListener('message', async (event) => { const userOp = decodeUserOp(event.detail.data) await mempool.add(userOp) // со всеми проверками })מתחת לעמלת הבסיס הנוכחית לא יעבור; ה-bundler עשוי לדחות או להפיל אותו זמנית.
class UserOpMempool {
private pool: Map<string, MempoolEntry> = new Map()
async add(userOp: UserOperation): Promise<string> {
const hash = getUserOpHash(userOp)
// Репутационная система: ограничение по sender/paymaster/factory
this.reputationManager.checkReputation(userOp)
this.pool.set(hash, {
userOp,
prefund: await this.calculatePrefund(userOp),
addedAt: Date.now()
})
return hash
}
getBundle(maxGas: bigint): UserOperation[] {
// Жадный алгоритм: выбрать UserOps с наибольшим priority fee
// с учётом лимита газа и конфликтов по storage
return this.selectNonConflicting(
[...this.pool.values()]
.sort((a, b) => Number(b.userOp.maxPriorityFeePerGas - a.userOp.maxPriorityFeePerGas)),
maxGas
)
}
} הגשת אצווה לרשת
ה-bundler יוצר אצווה של UserOps תקפים ושולח EntryPoint.handleOps(ops, beneficiary). beneficiary הוא הכתובת שאליה ה-EntryPoint ישלח את הגז שנאסף (עמלת העדיפות של ה-bundler).
נקודה קריטית: ה-bundler שולח עסקת EOA רגילה. הוא משלם גז מראש; ה-EntryPoint מחזיר מההפקדות של חשבונות/paymasters. אם handleOps חוזר, ה-bundler מפסיד גז. לכן, סימולציה לפני הגשה היא חובה.
הגנה מפני אצוות שחוזרות: ב-handleOps, ה-EntryPoint מדלג על UserOps שחוזרים בשלב הביצוע (לא אימות). לשלב האימות—אם הוא חוזר, כל handleOps נכשל. ה-bundler חייב להבטיח שהאימות יעבור בוודאות.
דצנטרליזציה והגנה: מוניטין ו-P2P
כדי למנוע ספאם והתקפות DoS, ERC-4337 מציג מערכת מוניטין עבור ישויות לא אסורות (Paymaster, Factory, Aggregator). הלוגיקה:
class ReputationManager {
// Для каждого entity отслеживаем: ops включённых vs ops неуспешных
updateIncluded(entity: string): void {
this.entries[entity].opsSeen++
this.entries[entity].opsIncluded++
}
updateFailed(entity: string): void {
this.entries[entity].opsIncluded-- // если bundle был reverted
}
getStatus(entity: string): 'ok' | 'throttled' | 'banned' {
const entry = this.entries[entity]
if (!entry) return 'ok'
const ratio = entry.opsIncluded / Math.max(1, entry.opsSeen)
if (ratio < MIN_INCLUSION_RATE_DENOMINATOR) return 'banned'
if (entry.opsSeen > THROTTLE_THRESHOLD) return 'throttled'
return 'ok'
}
}Staking ב-EntryPoint מגדיל את המגבלות: ישות עם stake יכולה לקבל יותר UserOps ב-mempool. זהו מנגנון אנטי-ספאם: אתה לא יכול לספאם את ה-mempool בחופשיות.
עבור bundler מבוזר, יש צורך ב-alt mempool מבוסס P2P—רשת להחלפת UserOps בין צמתי bundler. ERC-4337 מפרט פרוטוקול המבוסס על libp2p עם gossipsub:
- נושא:
user_ops/{chainId}/{entryPointAddress} - הודעה: UserOperation מקודד ב-RLP
- אימות: כל צומת מאמת באופן עצמאי לפני שידור
import { createLibp2p } from 'libp2p'
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
const libp2p = await createLibp2p({
/* ... transport, identify, etc */
services: {
pubsub: gossipsub({
allowPublishToZeroPeers: true,
msgIdFn: (msg) => computeUserOpHash(msg.data)
})
}
})
libp2p.services.pubsub.subscribe(userOpsTopic)
libp2p.services.pubsub.addEventListener('message', async (event) => {
const userOp = decodeUserOp(event.detail.data)
await mempool.add(userOp) // со всеми проверками
}) MEV ובניית אצווה
ל-bundler יש עמדה ייחודית: הוא בוחר את סדר ה-UserOps באצווה, מה שפותח הזדמנויות MEV. שתי אסטרטגיות:
Bundler FIFO הוגן—כולל UserOps בסדר שהתקבלו, ממקסם עמלת עדיפות. יישום פשוט, טוב עבור bundler מורשה ליישום ספציפי.
Bundler מודע ל-MEV—מנתח את ה-callData של UserOps, מוצא הזדמנויות ארביטראז', ובונה את האצווה בצורה אופטימלית. אינטגרציה עם Flashbots MEV-boost להגשת אצוות דרך mempool פרטי. חוסך 15-25% בעלויות גז עם אופטימיזציית MEV.
| אסטרטגיה | יתרונות | חסרונות |
|---|---|---|
| FIFO | פשטות, יכולת חיזוי | מפספס MEV |
| מודע ל-MEV | הכנסה נוספת | מורכב יותר, דורש ניתוח callData |
מה כלול בעבודה
- תיעוד—תיאור ארכיטקטורה, כללי גישת אחסון, סכמת אינטראקציה.
- גישה—למאגר, GitHub Actions, ניטור.
- הדרכה—סדנה על תפעול והגדרת ה-bundler.
- תמיכה—חודשיים לאחר ההשקה, כולל תיקוני באגים קריטיים.
- קוד מקור—תחת רישיון, עם הוראות פריסה.
יישומים מוכנים ל-Fork
- Infinitism/bundler (TypeScript)—יישום ייחוס מהיוצרים של ERC-4337
- Stackup bundler (Go)—bundler ייצור מ-Stackup
- Silius (Rust)—bundler בעל ביצועים גבוהים
- Rundler (Rust)—bundler מ-Alchemy
לפיתוח מותאם אישית: ייחוס TypeScript קל יותר להבנה; Rust/Go טובים יותר לתפוקת ייצור.
מחסנית טכנולוגית ולוחות זמנים
| רכיב | טכנולוגיה |
|---|---|
| שרת RPC | Node.js / Go / Rust |
| מעקב EVM | debug_traceCall + מעקב JS מותאם אישית |
| אחסון Mempool | Redis / בזיכרון + התמדה |
| P2P (אופציונלי) | libp2p + gossipsub |
| ניטור | Prometheus + Grafana |
| בדיקות | Foundry + Hardhat (EntryPoint מקומי) |
Bundler מרכזי בסיסי עם RPC, אימות, mempool, והגשת אצווה: 6-8 שבועות. הקושי העיקרי הוא מעקב EVM נכון עבור כללי גישת האחסון.
Bundler ייצור עם מערכת מוניטין, mempool P2P, אופטימיזציית MEV, ניטור: 3-4 חודשים.
אזהרה מרכזית: יישום לא נכון של כללי גישת האחסון מוביל או לקבלת UserOps מסוכנים (סיכון DoS) או לדחיית תקפים (UX גרוע). בדיקות יסודיות על כל מקרי הקצה הן חובה.
הזמן פיתוח bundler מותאם אישית—אנו ניישם אותו עבור התשתית שלך. צור קשר לייעוץ: נעזור לבחור אסטרטגיה ולחשב עלות עבור המשימה שלך.







