Разработка Account Abstraction Bundler
Bundler — центральный инфраструктурный компонент ERC-4337 экосистемы. Именно он обеспечивает работу Account Abstraction: принимает UserOperation от пользователей, валидирует их, группирует в batch и отправляет on-chain через EntryPoint.handleOps(). Без bundler-а Account Abstraction не работает — нет механизма доставки UserOps в блокчейн.
Разработка собственного bundler-а актуальна если: нужна кастомная мемпул логика, MEV оптимизация для UserOps, приватный bundler для конкретного приложения, или если требуется понимать и контролировать всю инфраструктуру ERC-4337.
Что делает Bundler: детальный флоу
Пользователь создаёт UserOperation и отправляет её в bundler через JSON-RPC метод eth_sendUserOperation. Bundler выполняет серию проверок, держит UserOp в своём alt mempool, и периодически отправляет batch on-chain.
Фаза 1: Validation
Bundler вызывает EntryPoint.simulateValidation(userOp). Это view-функция (reverts с результатом через custom error), которая:
- Если
initCodeне пустой — деплоит Account контракт через factory - Вызывает
account.validateUserOp()— проверяет подпись, nonce - Если указан Paymaster — вызывает
paymaster.validatePaymasterUserOp() - Возвращает
ValidationResultс данными о газе, стейкинге paymaster-а, временных ограничениях
interface ValidationResult {
returnInfo: {
preOpGas: bigint;
prefund: bigint; // сколько ETH account/paymaster депозировал в EntryPoint
sigFailed: boolean;
validAfter: number;
validUntil: number;
};
senderInfo: StakeInfo;
factoryInfo?: StakeInfo;
paymasterInfo?: StakeInfo;
}
prefund — ключевой момент. Account или Paymaster должны иметь депозит в EntryPoint достаточный для покрытия maxFeePerGas * (verificationGasLimit + callGasLimit). Bundler проверяет это до включения в mempool.
Storage Access Rules: почему это сложно
ERC-4337 накладывает жёсткие ограничения на то, какой storage может читать/писать validateUserOp. Цель — предотвратить ситуацию когда одна UserOp делает другие невалидными (griefing атака).
Запрещено в validation:
- Читать storage других контрактов, кроме самого Account и связанных entity
- Вызывать
block.timestamp,block.number(кроме ограниченного использования черезvalidAfter/validUntil) - Обращаться к storage, которое может быть изменено другой UserOp в том же batch
Bundler отслеживает storage slots, к которым обращается validation через debug_traceCall с EVM tracer. Это дорогостоящая операция — один из главных performance bottleneck-ов 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)
}
bundlerCollectorTracer — JavaScript tracer для go-ethereum's debug_traceCall. Отслеживает каждый SLOAD/SSTORE opcode и связывает их с вызывающим контрактом. Это самая технически нетривиальная часть bundler-а.
Фаза 2: Alt Mempool Management
UserOp принятая в mempool должна оставаться валидной. Bundler следит за:
Nonce invalidation. Если on-chain нонс Account изменился (другая UserOp прошла) — pending UserOp с устаревшим nonce удаляется.
Deposit insufficiency. Если balance депозита в EntryPoint уменьшился (другая UserOp спонсируемая тем же Paymaster-ом прошла) — нужно пересчитать хватит ли на все pending UserOps этого Paymaster-а.
Gas price changes. UserOp с maxFeePerGas ниже текущего basefee — не пройдёт, 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
)
}
}
Фаза 3: Bundle Submission
Bundler формирует batch из валидных UserOps и отправляет EntryPoint.handleOps(ops, beneficiary). beneficiary — адрес куда EntryPoint отправит собранный газ (priority fee bundler-а).
Критичный момент: bundler отправляет обычную EOA транзакцию. Он платит газ авансом, EntryPoint возмещает из депозитов Account/Paymaster. Если handleOps реверсируется — bundler теряет газ. Поэтому симуляция перед отправкой обязательна.
Защита от reverting bundle: EntryPoint в handleOps пропускает UserOps, которые реверсятся в execution phase (не в validation). Для validation фаза — если реверт, весь handleOps падает. Bundler должен убедиться что validation гарантированно пройдёт.
Репутационная система
Чтобы предотвратить spam и DoS атаки, ERC-4337 вводит reputation system для unbanned entities (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 повышает лимиты: entity со стейком может иметь больше UserOps в mempool. Это anti-spam механизм: нельзя бесплатно флудить mempool.
P2P Mempool
Для децентрализованного bundler-а нужен P2P alt mempool — сеть для обмена UserOps между bundler нодами. ERC-4337 специфицирует протокол на базе libp2p с gossipsub:
-
Topic:
user_ops/{chainId}/{entryPointAddress} - Message: RLP-encoded UserOperation
- Validation: каждый узел независимо валидирует перед relay
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 и Bundle строительство
Bundler имеет уникальную позицию: он выбирает порядок UserOps в bundle, что открывает MEV возможности. Две стратегии:
Честный FIFO bundler — включает UserOps в порядке получения, максимизирует priority fee. Простая реализация, хорошо для permissioned bundler конкретного приложения.
MEV-aware bundler — анализирует callData UserOps, находит арбитражные возможности, строит bundle оптимально. Интеграция с flashbots MEV-boost для отправки bundle через private mempool.
Имплементации для изучения и форка
- Infinitism/bundler (TypeScript) — reference implementation от создателей ERC-4337
- Stackup bundler (Go) — production bundler от Stackup
- Silius (Rust) — high-performance bundler
- Rundler (Rust) — bundler от Alchemy
Для кастомной разработки: TypeScript reference проще для понимания, Rust/Go лучше для production throughput.
Стек разработки
| Компонент | Технология |
|---|---|
| RPC сервер | Node.js / Go / Rust |
| EVM tracing | debug_traceCall + кастомный JS tracer |
| Mempool storage | Redis / in-memory + persistence |
| P2P (опционально) | libp2p + gossipsub |
| Мониторинг | Prometheus + Grafana |
| Тестирование | Foundry + Hardhat (local EntryPoint) |
Сроки
Базовый centralized bundler с RPC, validation, mempool и bundle submission: 6-8 недель. Основная сложность — корректный EVM tracer для storage access rules.
Production bundler с репутационной системой, P2P mempool, MEV оптимизацией, мониторингом: 3-4 месяца.
Главное предупреждение: некорректная реализация storage access rules ведёт к либо принятию опасных UserOps (DoS риск), либо к отклонению валидных (плохой UX). Тщательное тестирование на всех edge cases обязательно.







