ERC-4337 Bundler 完整實作指南:從原理到部署

深入解析 ERC-4337 帳戶抽象標準中 Bundler 的運作原理,提供完整的程式碼實作教學,包括操作收集、模擬執行、打包策略與主網部署指南,是參與帳戶抽象生態的開發者必讀指南。

ERC-4337 Bundler 完整實作指南:從原理到部署

概述

ERC-4337(帳戶抽象標準)是以太坊帳戶模型的重要革新,其核心創新是將帳戶驗證邏輯從共識層分離到應用層。在這個架構中,Bundler(捆綁器)是關鍵的基礎設施元件,負責收集用戶操作(UserOperation)、將其打包並提交到 EntryPoint 合約執行。本文深入解析 Bundler 的運作原理、核心元件的程式碼實作、以及部署與運維的最佳實踐。

理解 Bundler 的運作機制對於協議開發者、錢包提供商、以及希望深入參與帳戶抽象生態的開發者至關重要。我們將從基礎概念出發,逐步構建一個功能完整的 Bundler 實現。

一、ERC-4337 架構回顧

1.1 整體架構圖

ERC-4337 的設計避免了對以太坊共識層的任何修改,透過在應用層引入多個新元件實現帳戶抽象:

┌─────────────────────────────────────────────────────────────────┐
│                         UserOperation 流程                       │
│                                                                 │
│  ┌──────────┐    ┌──────────┐    ┌───────────┐    ┌────────┐ │
│  │  Client  │───▶│ Bundler  │───▶│EntryPoint │───▶│Account │ │
│  │ (錢包)   │    │          │    │  Contract │    │Contract│ │
│  └──────────┘    └──────────┘    └───────────┘    └────────┘ │
│       │              │                 │                │       │
│       │         ┌────┴────┐           │                │       │
│       │         │  Builder │           │                │       │
│       │         │  Network │           │                │       │
│       │         └──────────┘           │                │       │
│       │                                │                │       │
│  ┌────┴────┐                    ┌─────┴─────┐          │       │
│  │ Paymaster│                    │  Simulator│          │       │
│  │Contract │                    └───────────┘          │       │
│  └─────────┘                                                │       │
└─────────────────────────────────────────────────────────────┘

1.2 核心元件職責

EntryPoint 合約

Account 合約(Smart Account)

Bundler

Paymaster

二、Bundler 核心功能設計

2.1 UserOperation 結構

struct UserOperation {
    address sender;         // 錢包合約地址
    uint256 nonce;         // 防重放計數器
    bytes initCode;        // 初始化代碼(如果需要創建帳戶)
    bytes callData;        // 調用數據
    uint256 callGasLimit; // 調用 Gas 限制
    uint256 verificationGasLimit; // 驗證 Gas 限制
    uint256 preVerificationGas;   // 預驗證 Gas
    uint256 maxFeePerGas;  // 最大 Fee Per Gas
    uint256 maxPriorityFeePerGas; // 最大優先費用
    bytes paymasterAndData; // Paymaster 數據
    bytes signature;        // 用戶簽名
}

2.2 Bundler 基本流程

interface BundlerOptions {
    // EntryPoint 合約地址
    entryPointAddress: string;
    // RPC 節點 URL
    rpcUrl: string;
    // 私有鑰匙(用於簽名捆綁交易)
    privateKey: string;
    // 模擬服務 URL(可選)
    simulationServiceUrl?: string;
    // 最大打包數量
    maxBundleSize: number;
    // 打包間隔(毫秒)
    bundleInterval: number;
}

class Bundler {
    private options: BundlerOptions;
    private pendingOperations: Map<string, UserOperation>;
    private ethProvider: ethers.providers.JsonRpcProvider;
    private wallet: ethers.Wallet;

    constructor(options: BundlerOptions) {
        this.options = options;
        this.ethProvider = new ethers.providers.JsonRpcProvider(options.rpcUrl);
        this.wallet = new ethers.Wallet(options.privateKey, this.ethProvider);
        this.pendingOperations = new Map();
    }

    // 啟動 Bundler 服務
    async start() {
        console.log('Bundler starting...');

        // 啟動操作收集迴圈
        setInterval(() => this.processPendingOperations(), this.options.bundleInterval);

        // 監聽 mempool(替代方案:使用 RPC 方法)
        await this.setupMempoolListener();
    }

    private async setupMempoolListener() {
        // 監聽新的 UserOperation 事件
        // 實際實現會連接到各個錢包服務的 API
    }
}

2.3 操作收集與驗證

class Bundler {
    // 接收新的 UserOperation
    async addUserOperation(userOp: UserOperation): Promise<string> {
        // 1. 基本驗證
        this.validateUserOperation(userOp);

        // 2. 計算操作哈希
        const userOpHash = this.getUserOpHash(userOp);

        // 3. 檢查是否已存在
        if (this.pendingOperations.has(userOpHash)) {
            throw new Error('Operation already pending');
        }

        // 4. 模擬執行
        const simulationResult = await this.simulateUserOperation(userOp);
        if (!simulationResult.success) {
            throw new Error(`Simulation failed: ${simulationResult.revertReason}`);
        }

        // 5. 驗證有效期限
        const validAfter = userOp.nonce >> 8;
        const validUntil = userOp.nonce & 0xff;
        // 檢查時間有效性...

        // 6. 加入待處理池
        this.pendingOperations.set(userOpHash, userOp);

        return userOpHash;
    }

    private validateUserOperation(userOp: UserOperation): void {
        // 基本結構驗證
        if (!userOp.sender) throw new Error('Missing sender');
        if (!userOp.signature) throw new Error('Missing signature');
        if (userOp.callGasLimit < 21000) throw new Error('Invalid callGasLimit');

        // 費用驗證
        const minFee = await this.calculateMinFee(userOp);
        if (userOp.maxFeePerGas < minFee) {
            throw new Error('Insufficient fee');
        }
    }

    private async calculateMinFee(userOp: UserOperation): Promise<bigint> {
        // 計算基於網路當前費用的最低費用
        const block = await this.ethProvider.getBlock('latest');
        const baseFee = block.baseFeePerGas || 0n;
        return baseFee + 1_000_000_000n; // min 1 gwei priority
    }
}

三、模擬執行引擎

3.1 模擬的重要性

在將 UserOperation 提交到區塊鏈之前,Bundler 必須模擬執行以驗證:

3.2 模擬器實現

interface SimulationResult {
    success: boolean;
    gasUsed: bigint;
    revertReason?: string;
    validAfter?: number;
    validUntil?: number;
    paymasterReward?: bigint;
}

class OperationSimulator {
    private provider: ethers.providers.JsonRpcProvider;
    private entryPointAddress: string;

    constructor(provider: ethers.providers.JsonRpcProvider, entryPoint: string) {
        this.provider = provider;
        this.entryPointAddress = entryPoint;
    }

    async simulate(
        userOp: UserOperation,
        entryPointCode: string
    ): Promise<SimulationResult> {
        // 1. 準備模擬環境
        const simulationState = await this.prepareSimulation(userOp);

        // 2. 執行驗證階段
        const verificationResult = await this.executeVerification(
            userOp,
            simulationState
        );

        if (!verificationResult.success) {
            return {
                success: false,
                gasUsed: verificationResult.gasUsed,
                revertReason: verificationResult.revertReason
            };
        }

        // 3. 執行調用階段
        const callResult = await this.executeCall(userOp, simulationState);

        return {
            success: callResult.success,
            gasUsed: verificationResult.gasUsed + callResult.gasUsed,
            revertReason: callResult.revertReason,
            validAfter: userOp.nonce >> 8,
            validUntil: userOp.nonce & 0xff
        };
    }

    private async prepareSimulation(userOp: UserOperation): Promise<SimulationState> {
        // 創建模擬所需的狀態快照
        const state = {
            blockNumber: await this.provider.getBlockNumber(),
            blockGasLimit: (await this.provider.getBlock('latest')).gasLimit,
            balances: new Map<string, bigint>(),
            storage: new Map<string, string>()
        };

        // 記錄餘額
        state.balances.set(
            userOp.sender,
            await this.provider.getBalance(userOp.sender)
        );
        state.balances.set(
            this.entryPointAddress,
            await this.provider.getBalance(this.entryPointAddress)
        );

        return state;
    }

    private async executeVerification(
        userOp: UserOperation,
        state: SimulationState
    ): Promise<{ success: boolean; gasUsed: bigint; revertReason?: string }> {
        // 使用 eth_call 模擬驗證
        // 實際會調用 EntryPoint.simulateValidation
        try {
            const result = await this.provider.call({
                to: this.entryPointAddress,
                data: this.encodeSimulateValidation(userOp)
            });

            // 解碼結果
            const [success, validAfter, validUntil, authorizer, paymasterReward, gasUsed] =
                this.decodeSimulateValidationResult(result);

            return {
                success,
                gasUsed,
                revertReason: success ? undefined : 'Validation failed'
            };
        } catch (error: any) {
            return {
                success: false,
                gasUsed: 0n,
                revertReason: error.message
            };
        }
    }

    private async executeCall(
        userOp: UserOperation,
        state: SimulationState
    ): Promise<{ success: boolean; gasUsed: bigint; revertReason?: string }> {
        try {
            // 模擬實際的用戶調用
            const result = await this.provider.call({
                to: this.entryPointAddress,
                data: this.encodeSimulateOp(userOp)
            });

            // 解析返回結果
            return this.decodeSimulateOpResult(result);
        } catch (error: any) {
            return {
                success: false,
                gasUsed: 0n,
                revertReason: error.message
            };
        }
    }

    // 編碼輔助函數
    private encodeSimulateValidation(userOp: UserOperation): string {
        // 實際實現需要正確編碼
        return '0x'; // 簡化
    }

    private encodeSimulateOp(userOp: UserOperation): string {
        return '0x'; // 簡化
    }

    private decodeSimulateValidationResult(result: string): any[] {
        return [true, 0, 0, 0, 0, 50000n]; // 簡化
    }

    private decodeSimulateOpResult(result: string): any {
        return { success: true, gasUsed: 30000n }; // 簡化
    }
}

3.3 Gas 估計邏輯

class GasEstimator {
    // 估算操作的總 Gas 需求
    async estimateTotalGas(userOp: UserOperation): Promise<{
        verificationGas: bigint;
        callGas: bigint;
        preVerificationGas: bigint;
        totalGas: bigint;
    }> {
        // 1. 估算驗證 Gas
        const verificationGas = await this.estimateVerificationGas(userOp);

        // 2. 估算調用 Gas
        const callGas = await this.estimateCallGas(userOp);

        // 3. 估算預驗證 Gas
        const preVerificationGas = this.estimatePreVerificationGas(userOp);

        // 4. 添加緩衝
        const buffer = 120n; // 20% 緩衝
        const totalGas =
            (verificationGas + callGas + preVerificationGas) * buffer / 100n;

        return {
            verificationGas,
            callGas,
            preVerificationGas,
            totalGas
        };
    }

    private async estimateVerificationGas(userOp: UserOperation): Promise<bigint> {
        // 基本驗證開銷
        const baseVerification = 50000n;

        // 初始化新帳戶的額外開銷
        if (userOp.initCode.length > 0) {
            // 計算創建帳戶的成本
            const create2Cost = 20000n;
            const initCodeGas = userOp.initCode.length * 16n; // 每 byte 16 gas
            return baseVerification + create2Cost + initCodeGas;
        }

        return baseVerification;
    }

    private async estimateCallGas(userOp: UserOperation): Promise<bigint> {
        // 使用用戶提供的限制作為起點
        return BigInt(userOp.callGasLimit);
    }

    private estimatePreVerificationGas(userOp: UserOperation): bigint {
        // UserOperation 本身的 Gas 開銷
        const overhead = 21000n;

        // Calldata 成本
        const calldataCost = BigInt(userOp.callData.length * 16n);

        return overhead + calldataCost;
    }
}

四、打包與提交邏輯

4.1 打包策略

interface BundleCandidate {
    userOp: UserOperation;
    hash: string;
    gasEstimate: bigint;
    feePerGas: bigint;
}

class BundlePacker {
    private maxBundleSize: number;
    private maxBundleGas: bigint;
    private ethProvider: ethers.providers.JsonRpcProvider;

    constructor(maxSize: number, maxGas: bigint, provider: ethers.providers.JsonRpcProvider) {
        this.maxBundleSize = maxSize;
        this.maxBundleGas = maxGas;
        this.ethProvider = provider;
    }

    // 選擇最佳操作組合
    async selectOperations(
        operations: Map<string, UserOperation>
    ): Promise<UserOperation[]> {
        // 1. 獲取當前區塊資訊
        const block = await this.ethProvider.getBlock('latest');
        const baseFee = block?.baseFeePerGas || 10n * 10n ** 9n;

        // 2. 轉換為候選對象
        const candidates: BundleCandidate[] = [];

        for (const [hash, userOp] of operations) {
            const gasEstimate = await this.estimateOperationGas(userOp);
            const feePerGas = this.calculateEffectiveFee(userOp, baseFee);

            candidates.push({
                userOp,
                hash,
                gasEstimate,
                feePerGas
            });
        }

        // 3. 按 Gas 效率排序(fee / gas)
        candidates.sort((a, b) => {
            const aEfficiency = a.feePerGas / a.gasEstimate;
            const bEfficiency = b.feePerGas / b.gasEstimate;
            return bEfficiency > aEfficiency ? 1 : -1;
        });

        // 4. 貪心選擇最優組合
        const selected: UserOperation[] = [];
        let totalGas = 0n;

        for (const candidate of candidates) {
            if (
                selected.length >= this.maxBundleSize ||
                totalGas + candidate.gasEstimate > this.maxBundleGas
            ) {
                break;
            }

            selected.push(candidate.userOp);
            totalGas += candidate.gasEstimate;
        }

        return selected;
    }

    private async estimateOperationGas(userOp: UserOperation): Promise<bigint> {
        // 調用模擬器獲取估計
        // 簡化實現
        return BigInt(
            userOp.verificationGasLimit +
            userOp.callGasLimit +
            userOp.preVerificationGas
        );
    }

    private calculateEffectiveFee(userOp: UserOperation, baseFee: bigint): bigint {
        const priorityFee = BigInt(userOp.maxPriorityFeePerGas);
        const maxFee = BigInt(userOp.maxFeePerGas);
        return maxFee < baseFee + priorityFee ? maxFee : baseFee + priorityFee;
    }
}

4.2 提交到 EntryPoint

class BundlerExecutor {
    private entryPoint: ethers.Contract;
    private wallet: ethers.Wallet;
    private ethProvider: ethers.providers.JsonRpcProvider;

    constructor(
        entryPointAddress: string,
        wallet: ethers.Wallet,
        provider: ethers.providers.JsonRpcProvider
    ) {
        this.entryPoint = new ethers.Contract(
            entryPointAddress,
            EntryPointABI,
            wallet
        );
        this.wallet = wallet;
        this.ethProvider = provider;
    }

    // 執行打包的 UserOperation
    async executeBundle(operations: UserOperation[]): Promise<string> {
        if (operations.length === 0) {
            throw new Error('No operations to execute');
        }

        // 1. 計算總 Gas 需求
        const totalGas = await this.estimateTotalGas(operations);

        // 2. 檢查餘額
        const balance = await this.ethProvider.getBalance(this.wallet.address);
        const requiredBalance = totalGas * this.getMaxFee(operations);

        if (balance < requiredBalance) {
            throw new Error('Insufficient balance for gas');
        }

        // 3. 獲取 nonce
        const nonce = await this.entryPoint.nonce(this.wallet.address, 0);

        // 4. 構建交易
        const tx = await this.entryPoint.handleOps(operations, this.wallet.address, {
            gasLimit: this.adjustGasLimit(totalGas),
            maxFeePerGas: this.getMaxFee(operations),
            maxPriorityFeePerGas: this.getMaxPriorityFee(operations)
        });

        console.log(`Bundle submitted: ${tx.hash}`);

        // 5. 等待確認
        const receipt = await tx.wait();

        // 6. 分析結果
        return this.analyzeExecutionResult(receipt, operations);
    }

    private async estimateTotalGas(operations: UserOperation[]): Promise<bigint> {
        // 基礎開銷 + 所有操作的 Gas 限制
        let total = 21000n; // base transaction

        for (const op of operations) {
            total += BigInt(op.verificationGasLimit);
            total += BigInt(op.callGasLimit);
            total += BigInt(op.preVerificationGas);
        }

        return total;
    }

    private getMaxFee(operations: UserOperation[]): bigint {
        let maxFee = 0n;
        for (const op of operations) {
            const fee = BigInt(op.maxFeePerGas);
            if (fee > maxFee) maxFee = fee;
        }
        return maxFee;
    }

    private getMaxPriorityFee(operations: UserOperation[]): bigint {
        let maxFee = 0n;
        for (const op of operations) {
            const fee = BigInt(op.maxPriorityFeePerGas);
            if (fee > maxFee) maxFee = fee;
        }
        return maxFee;
    }

    private adjustGasLimit(estimated: bigint): bigint {
        // 添加 30% 緩衝
        return estimated * 130n / 100n;
    }

    private analyzeExecutionResult(
        receipt: ethers.providers.TransactionReceipt,
        operations: UserOperation[]
    ): string {
        // 解析 Logs 獲取執行結果
        const userOpEvents = receipt.logs.filter(
            log => log.address.toLowerCase() === this.entryPoint.address.toLowerCase()
        );

        // 統計成功/失敗數量
        let successCount = 0;
        let failureCount = 0;

        for (const event of userOpEvents) {
            // 解析 UserOperationEvent
            // success = event.args.success
            // ...
        }

        return `Bundle complete: ${successCount} success, ${failureCount} failed`;
    }
}

五、Paymaster 整合

5.1 Paymaster 類型

// 支援的 Paymaster 類型
enum PaymasterType {
    None = 'none',           // 不使用 Paymaster
    Verifying = 'verifying', // 驗證型:需要提前存入資金
    ERC20 = 'erc20'          // ERC20 代幣支付
}

interface PaymasterConfig {
    type: PaymasterType;
    address?: string;
    tokenAddress?: string;
    // 驗證型 Paymaster 專用
    verifyingSigner?: string;
    // ERC20 Paymaster 專用
    exchangeProxy?: string;
}

5.2 Paymaster 驗證

class PaymasterValidator {
    // 驗證 Paymaster 條件是否滿足
    async validate(
        userOp: UserOperation,
        paymasterConfig: PaymasterConfig
    ): Promise<{ valid: boolean; reason?: string }> {
        if (paymasterConfig.type === PaymasterType.None) {
            return { valid: true };
        }

        if (paymasterConfig.type === PaymasterType.ERC20) {
            return this.validateERC20Paymaster(userOp, paymasterConfig);
        }

        return { valid: true };
    }

    private async validateERC20Paymaster(
        userOp: UserOperation,
        config: PaymasterConfig
    ): Promise<{ valid: boolean; reason?: string }> {
        if (!config.tokenAddress || !config.exchangeProxy) {
            return { valid: false, reason: 'Missing ERC20 config' };
        }

        // 檢查用戶在 Paymaster 的存款餘額
        const token = new ethers.Contract(
            config.tokenAddress,
            ERC20ABI,
            this.provider
        );

        const deposit = await token.balanceOf(userOp.sender);
        const requiredDeposit = this.estimateGasCost(userOp);

        if (deposit < requiredDeposit) {
            return {
                valid: false,
                reason: `Insufficient token balance. Have: ${deposit}, Need: ${requiredDeposit}`
            };
        }

        return { valid: true };
    }

    private estimateGasCost(userOp: UserOperation): bigint {
        const gasLimit =
            userOp.verificationGasLimit +
            userOp.callGasLimit +
            userOp.preVerificationGas;
        return BigInt(gasLimit) * BigInt(userOp.maxFeePerGas);
    }
}

六、完整 Bundler 服務實現

6.1 主服務類

// main.ts - Bundler 服務主入口

import { ethers } from 'ethers';
import { Bundler } from './bundler';
import { OperationSimulator } from './simulator';
import { BundlePacker } from './packer';
import { BundlerExecutor } from './executor';

class AccountAbstractionBundler {
    private bundler: Bundler;
    private simulator: OperationSimulator;
    private packer: BundlePacker;
    private executor: BundlerExecutor;

    constructor(config: BundlerConfig) {
        // 初始化 provider
        const provider = new ethers.providers.JsonRpcProvider(config.rpcUrl);

        // 初始化錢包
        const wallet = new ethers.Wallet(config.privateKey, provider);

        // 初始化各個元件
        this.simulator = new OperationSimulator(provider, config.entryPoint);

        this.bundler = new Bundler({
            ...config,
            provider,
            simulator: this.simulator
        });

        this.packer = new BundlePacker(
            config.maxBundleSize,
            BigInt(config.maxBundleGas),
            provider
        );

        this.executor = new BundlerExecutor(
            config.entryPoint,
            wallet,
            provider
        );
    }

    async start(): Promise<void> {
        console.log('Starting Account Abstraction Bundler...');

        // 啟動 HTTP API 伺服器
        await this.startHttpServer();

        // 啟動操作處理迴圈
        await this.startOperationProcessor();

        console.log('Bundler started successfully');
    }

    private async startHttpServer(): Promise<void> {
        // 使用 Express 或其他框架
        // 暴露以下 API:
        // - POST /rpc - 處理 JSON-RPC 請求
        // - GET /health - 健康檢查
        // - GET /stats - 統計資訊
    }

    private async startOperationProcessor(): Promise<void> {
        // 定時處理待處理的 UserOperation
        setInterval(async () => {
            try {
                await this.processBundle();
            } catch (error) {
                console.error('Bundle processing error:', error);
            }
        }, 10000); // 每 10 秒處理一次
    }

    private async processBundle(): Promise<void> {
        // 1. 選擇要打包的操作
        const operations = await this.packer.selectOperations(
            this.bundler.getPendingOperations()
        );

        if (operations.length === 0) {
            return; // 沒有操作可打包
        }

        // 2. 再次模擬驗證
        for (const op of operations) {
            const result = await this.simulator.simulate(op, '');
            if (!result.success) {
                console.log(`Removing invalid operation: ${result.revertReason}`);
                this.bundler.removeOperation(op);
            }
        }

        // 3. 執行打包
        try {
            const txHash = await this.executor.executeBundle(operations);

            // 4. 清理已執行的操作
            for (const op of operations) {
                this.bundler.removeOperation(op);
            }

            console.log(`Bundle executed: ${txHash}`);
        } catch (error) {
            console.error('Execution failed:', error);
        }
    }
}

interface BundlerConfig {
    rpcUrl: string;
    privateKey: string;
    entryPoint: string;
    maxBundleSize: number;
    maxBundleGas: number;
    port: number;
}

// 啟動服務
const config: BundlerConfig = {
    rpcUrl: process.env.RPC_URL || 'https://eth.llamarpc.com',
    privateKey: process.env.BUNDLER_PRIVATE_KEY || '',
    entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789',
    maxBundleSize: 10,
    maxBundleGas: 3000000,
    port: 3000
};

const bundler = new AccountAbstractionBundler(config);
bundler.start();

6.2 JSON-RPC API 實現

// rpc-handler.ts

import { Request, Response } from 'express';

class RpcHandler {
    private bundler: AccountAbstractionBundler;

    constructor(bundler: AccountAbstractionBundler) {
        this.bundler = bundler;
    }

    // 處理 eth_sendUserOperation
    async handleSendUserOperation(req: Request, res: Response): Promise<void> {
        try {
            const { userOperation, entryPoint } = req.body;

            // 1. 驗證 EntryPoint
            if (entryPoint !== this.config.entryPoint) {
                res.status(400).json({
                    error: 'Invalid entryPoint'
                });
                return;
            }

            // 2. 新增到待處理池
            const userOpHash = await this.bundler.addUserOperation(userOperation);

            // 3. 返回哈希
            res.json({
                jsonrpc: '2.0',
                id: req.body.id,
                result: userOpHash
            });
        } catch (error: any) {
            res.status(400).json({
                jsonrpc: '2.0',
                id: req.body.id,
                error: {
                    code: -32500,
                    message: error.message
                }
            });
        }
    }

    // 處理 eth_estimateUserOperationGas
    async handleEstimateGas(req: Request, res: Response): Promise<void> {
        try {
            const { userOperation, entryPoint } = req.body;

            const estimate = await this.bundler.estimateGas(userOperation);

            res.json({
                jsonrpc: '2.0',
                id: req.body.id,
                result: {
                    preVerificationGas: estimate.preVerificationGas.toString(),
                    verificationGasLimit: estimate.verificationGas.toString(),
                    callGasLimit: estimate.callGas.toString()
                }
            });
        } catch (error: any) {
            res.status(400).json({
                jsonrpc: '2.0',
                id: req.body.id,
                error: {
                    code: -32501,
                    message: error.message
                }
            });
        }
    }

    // 處理 eth_getUserOperationByHash
    async handleGetUserOperationByHash(req: Request, res: Response): Promise<void> {
        const { hash } = req.params;

        const userOp = await this.bundler.getUserOperationByHash(hash);

        if (!userOp) {
            res.status(404).json({
                jsonrpc: '2.0',
                id: req.body.id,
                error: {
                    code: -32502,
                    message: 'UserOperation not found'
                }
            });
            return;
        }

        res.json({
            jsonrpc: '2.0',
            id: req.body.id,
            result: userOp
        });
    }
}

七、部署與運維

7.1 部署配置

# docker-compose.yml
version: '3.8'

services:
  bundler:
    image: ethereum/bundler:latest
    ports:
      - "3000:3000"
    environment:
      - RPC_URL=${RPC_URL}
      - PRIVATE_KEY=${BUNDLER_PRIVATE_KEY}
      - ENTRY_POINT=0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
      - MAX_BUNDLE_SIZE=10
      - MAX_BUNDLE_GAS=3000000
      - PORT=3000
    volumes:
      - ./data:/app/data
    restart: unless-stopped

  # 可選:Redis 用於跨實例共享狀態
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data

volumes:
  redis-data:

7.2 監控指標

// metrics.ts

class BundlerMetrics {
    private counters: Map<string, number> = new Map();
    private gauges: Map<string, number> = new Map();
    private histograms: Map<string, number[]> = new Map();

    // 記錄收到的操作數
    recordOperationReceived(op: UserOperation): void {
        this.incrementCounter('operations_received_total');
    }

    // 記錄成功執行的操作
    recordOperationSuccess(op: UserOperation, gasUsed: bigint): void {
        this.incrementCounter('operations_success_total');
        this.recordHistogram('operations_gas_used', Number(gasUsed));
    }

    // 記錄失敗的操作
    recordOperationFailure(op: UserOperation, reason: string): void {
        this.incrementCounter('operations_failed_total');
    }

    // 更新待處理隊列大小
    updatePendingQueueSize(size: number): void {
        this.setGauge('pending_queue_size', size);
    }

    private incrementCounter(name: string): void {
        const current = this.counters.get(name) || 0;
        this.counters.set(name, current + 1);
    }

    private setGauge(name: string, value: number): void {
        this.gauges.set(name, value);
    }

    private recordHistogram(name: string, value: number): void {
        const values = this.histograms.get(name) || [];
        values.push(value);
        if (values.length > 1000) values.shift();
        this.histograms.set(name, values);
    }

    // 導出 Prometheus 格式
    toPrometheus(): string {
        let output = '';

        for (const [name, value] of this.counters) {
            output += `# TYPE ${name} counter\n`;
            output += `${name} ${value}\n`;
        }

        for (const [name, value] of this.gauges) {
            output += `# TYPE ${name} gauge\n`;
            output += `${name} ${value}\n`;
        }

        return output;
    }
}

7.3 安全考量

class BundlerSecurity {
    // Rate Limiting
    private rateLimiter: RateLimiter;

    // 惡意操作檢測
    private maliciousPatterns: RegExp[] = [
        // 檢測 selfdestruct 模式
        /selfdestruct/i,
        // 檢測可疑的 delegatecall
        /delegatecall.*0x/i
    ];

    // 驗證操作安全性
    async validateSecurity(userOp: UserOperation): Promise<{
        allowed: boolean;
        reason?: string;
    }> {
        // 1. Rate Limiting
        if (!this.rateLimiter.check(userOp.sender)) {
            return { allowed: false, reason: 'Rate limit exceeded' };
        }

        // 2. 惡意模式檢測
        const callData = userOp.callData.toString();
        for (const pattern of this.maliciousPatterns) {
            if (pattern.test(callData)) {
                return { allowed: false, reason: 'Suspicious pattern detected' };
            }
        }

        // 3. Gas 限制
        const totalGas = userOp.verificationGasLimit +
            userOp.callGasLimit +
            userOp.preVerificationGas;

        if (totalGas > 30_000_00) { // 30M gas limit
            return { allowed: false, reason: 'Gas limit too high' };
        }

        return { allowed: true };
    }
}

八、常見問題與故障排除

8.1 模擬失敗

問題:UserOperation 模擬失敗

排查步驟

  1. 檢查 EntryPoint 合約地址是否正確
  2. 驗證錢包合約是否已部署
  3. 檢查簽名是否有效
  4. 確認 nonce 正確

8.2 Gas 估算錯誤

問題:實際 Gas 消耗超過估算

解決方案

8.3 競爭條件

問題:多個 Bundler 打包相同操作

解決方案

結論

Bundler 是 ERC-4337 生態系統中不可或缺的基礎設施。透過本文的詳細講解,讀者應該能夠理解 Bundler 的核心運作原理,並具備構建功能完整 Bundler 的能力。

關鍵要點回顧:

隨著帳戶抽象技術的普及,Bundler 將成為 Web3 基礎設施的重要組成部分。建議開發者持續關注 ERC-4337 的演進,並積極參與生態建設。

延伸閱讀

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!