פיתוח Bundler להפשטת חשבונות ERC-4337

ללא bundler אמין, Account Abstraction נכשלת: UserOperations לא מגיעות ל-blockchain, ועסקים מאבדים משתמשים. אנחנו מפתחים bundlers של ERC-4337 המותאמים לצרכים שלכם—מבסיסיים ועד מותאמי MEV עם mempool פרטי. הצוות שלנו מספק פתרונות turnkey, מטפל בכל ניואנסים של אימות ואבטחה, ומבטיח פעילות יציבה עם תמיכה מתמשכת.

שירותי פיתוח בלוקצ'יין

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1481
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1335
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1034
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1293
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    738
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1031

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 (חוזרת עם תוצאה דרך שגיאה מותאמת אישית) ש:

  1. אם 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.
  2. קוראת ל-prefund—בודקת חתימה, nonce.
  3. אם צוין Paymaster—קוראת ל-maxFeePerGas * (verificationGasLimit + callGasLimit).
  4. מחזירה 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

מה כלול בעבודה

  1. תיעוד—תיאור ארכיטקטורה, כללי גישת אחסון, סכמת אינטראקציה.
  2. גישה—למאגר, GitHub Actions, ניטור.
  3. הדרכה—סדנה על תפעול והגדרת ה-bundler.
  4. תמיכה—חודשיים לאחר ההשקה, כולל תיקוני באגים קריטיים.
  5. קוד מקור—תחת רישיון, עם הוראות פריסה.

יישומים מוכנים ל-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 מותאם אישית—אנו ניישם אותו עבור התשתית שלך. צור קשר לייעוץ: נעזור לבחור אסטרטגיה ולחשב עלות עבור המשימה שלך.