---
# System prepended metadata

title: A MultiLayer2 Bundler

---

# A MultiLayer2 Bundler
最近在和PlanckerDao的小伙伴一起研究ERC4337相关技术，我们打算利用Infinitism的源码搭建一套Layer2上的多链bundler服务。

首先，Entrypoint和账户相关的合约必须部署在链上。目前Entrypoint在很多链上已经部署过，所以现在只需要部署账户相关的合约到链上。我们选择部署SoulWallet的账户合约来进行测试，具体的部署步骤是：
* 一个单例工厂SingletonFactory：https://mumbai.polygonscan.com/address/0xce0042b868300000d44a59004da54a005ffdcf9f
* 一个账户合约SoulWallet：https://mumbai.polygonscan.com/address/0x0F8065973c4F7AB41E739302152c5cB6aC7590BA
* 一个账户工厂合约SoulWalletFactory：https://mumbai.polygonscan.com/address/0xC544A5107d887c9df046Cd8C5fB9D61e7559c229

有了这三个部署好的合约，就可以开始激活一个账户了，部分代码如下：
```
async function activateWallet() {
        const { chainId } = await loadFixture(deployFixture);
        
        const upgradeDelay = 30;
        const guardianDelay = 30;

        const walletAddress = await soulWalletLib.calculateWalletAddress(
            walletLogicAddress,
            entryPointAddress,
            walletOwner.address,
            upgradeDelay,
            guardianDelay,
            SoulWalletLib.Defines.AddressZero,
            slat
        );

        log('walletAddress: ' + walletAddress);
        log('walletBalance: ' + await provider.getBalance(walletAddress), 'wei');
        
        //#region
        const feeData = await provider.getFeeData()
        const activateOp = soulWalletLib.activateWalletOp(
            walletLogicAddress,
            entryPointAddress,
            walletOwner.address,
            upgradeDelay,
            guardianDelay,
            SoulWalletLib.Defines.AddressZero,
            '0x',
            feeData.maxFeePerGas?.toNumber() as NumberLike,
            feeData.maxPriorityFeePerGas?.toNumber() as NumberLike,
            slat
        );
        await estimateUserOperationGas(bundler, activateOp);

        const requiredPrefund = await (await activateOp.requiredPrefund()).requiredPrefund;
        log('requiredPrefund: ', ethers.utils.formatEther(requiredPrefund));


        const balance = await provider.getBalance(walletAddress);
        log('walletBalance: ' + balance, 'wei');

        const userOpHash = activateOp.getUserOpHashWithTimeRange(entryPointAddress, chainId, walletOwner.address);
        activateOp.signWithSignature(
            walletOwner.address,
            Utils.signMessage(userOpHash, walletOwner.privateKey)
        );

        const validation = await bundler.simulateValidation(activateOp);
        if (validation.status !== 0) {
            throw new Error(`error code:${validation.status}`);
        }
        log(`simulateValidation result:`, validation);
        const simulate = await bundler.simulateHandleOp(activateOp);
        if (simulate.status !== 0) {
            throw new Error(`error code:${simulate.status}`);
        }
        log(`simulateHandleOp result:`, simulate);

        
        let activated = false;
        const bundlerEvent = bundler.sendUserOperation(activateOp, 1000 * 60 * 5);
        bundlerEvent.on('error', (err: any) => {
            console.log(err);
        });
        bundlerEvent.on('send', (userOpHash: string) => {
            console.log('send: ' + userOpHash);
        });
        bundlerEvent.on('receipt', (receipt: IUserOpReceipt) => {
            console.log('receipt: ' + receipt);
        activated = true;
        
        });
        bundlerEvent.on('timeout', () => {
            console.log('timeout');
        });
        while (!activated) {
            console.log("send userOperration, waiting...");
            await new Promise(r => setTimeout(r, 3000));
        }
        
        const walletAddressCode = await provider.getCode(walletAddress);
        log('walletAddressCode: ' + walletAddressCode);

        return {
            walletAddress
        }

    }
```

需要注意的是生成的账户地址一开始里面是没有余额的，需要在生成这个账户地址后往里面转一些币。然后执行激活程序，没有意外的话就得到了一个智能合约账户，然后通过这个账户可以进行转账等操作，下面是转账部分代码：
```
async function transferEth() {
        const { chainId } = await loadFixture(deployFixture);
        const walletAddress  = accounts[0];
        
        
        let nonce = await soulWalletLib.Utils.getNonce(walletAddress, provider);
        const feeData = await provider.getFeeData()
        const sendETHOP = await soulWalletLib.Tokens.ETH.transfer(
            walletAddress,
            nonce,
            '0x',
            feeData.maxFeePerGas?.toNumber() as NumberLike,
            feeData.maxPriorityFeePerGas?.toNumber() as NumberLike,
            accounts[1],
            ethers.utils.parseEther('0.00001').toHexString()
        );
        if (!sendETHOP) {
            throw new Error('setGuardianOP is null');
        }

        await estimateUserOperationGas(bundler, sendETHOP);

        const requiredPrefund = await (await sendETHOP.requiredPrefund()).requiredPrefund;
        log('requiredPrefund: ', ethers.utils.formatEther(requiredPrefund));

        const sendETHOPuserOpHash = sendETHOP.getUserOpHashWithTimeRange(entryPointAddress, chainId, walletOwner.address);
        const sendETHOPSignature = Utils.signMessage(sendETHOPuserOpHash, walletOwner.privateKey)
        sendETHOP.signWithSignature(walletOwner.address, sendETHOPSignature);

        let validation = await bundler.simulateValidation(sendETHOP);
        if (validation.status !== 0) {
            throw new Error(`error code:${validation.status}`);
        }
        let simulate = await bundler.simulateHandleOp(sendETHOP);
        if (simulate.status !== 0) {
            throw new Error(`error code:${simulate.status}`);
        }

        // get balance of accounts[1].address
        let finish = false
        const balanceBefore = await provider.getBalance(accounts[1]);
        console.log('balanceBefore: ' + balanceBefore);

        
        
        const bundlerEvent = bundler.sendUserOperation(sendETHOP, 1000 * 60 * 5);
        bundlerEvent.on('error', (err: any) => {
            console.log(err);
        });
        bundlerEvent.on('send', (userOpHash: string) => {
            console.log('send: ' + userOpHash);
        });
        bundlerEvent.on('receipt', (receipt: IUserOpReceipt) => {
            console.log('receipt: ' + receipt);
            finish = true
        });
        bundlerEvent.on('timeout', () => {
            console.log('timeout');
        });

        while (!finish) {
            console.log("send userOperration, waiting...");
            await new Promise(r => setTimeout(r, 3000));
        }
        
        
        // get balance of accounts[1].address
        const balanceAfter = await provider.getBalance(accounts[1]);
        console.log('balanceAfter: ' + balanceAfter);
        
        console.log('diffAmount:' + balanceAfter.sub(balanceBefore).toString());


    }
```

这样的话，我们就在polygon mumbai上成功的通过soulwallet和bundler激活了智能合约账户。然后我们又在polygon主网、arbitrum主网、optimistic主网上重复同样的操作。然后我们又把这些流畅能跑通的不同网络的bundler服务分别部署到了服务器上，这样有需要的人也可以使用这些bundler服务了。

# 后期计划
当然目前的服务只能用于简单测试激活账户、转账操作，真正稳定运行还需要很多优化。后面我们将
* 真正在Layer2部署bundler服务，并在前端启用RPC服务
* 另外我们计划对替代内存池的验证规则进行研究
* 以及Ban policy定制化机制和开发。

# 遇到的问题
下面是我们在测试过程中碰到的一些问题：
* 在polygon主网激活时，一直提示gas相关信息（更改了不同gas返回的信息）:

    replacement transaction underpriced
    Replacement UserOperation must have higher maxFeePerGas
    transaction underpriced

    然后使用Infinitism账户合约进行测试，情况同上。后来通过排查代码发现，当bundler向节点发送请求时，也设置的有gas相关信息。之前只是调整了userOp的gas数据，所以怎么调整都没用。Infinitsm的bundler代码使用provider.getFeeData()来设置gas，但是这个 feeData.maxPriorityFeePerGas 总是返回 1500000000，所以必须增加 maxPriorityFeePerGas的值才能成功。![](https://i.imgur.com/Ea84mbr.png)
packages/bundler/src/modules/BundleManager.ts 86行，后面这个地方的gas可以再优化。

* 在测试optimistic主网的时候发现返回的错误信息不同，导致bundler服务启动不了。解决办法是在packages/bundler/src/BundlerServer.ts 77行，在if中添加一个“err?.errorName &&”。这个可能是因为Optimism网络的错误信息格式不太一样，在没有错误的时候，是没有errorName的。![](https://i.imgur.com/A1Ib5cP.png)


* 还有一些和bundler不太相关的问题，看到的人可能有帮助。比如：
在服务器上运行测试脚本总是报告“未定义 AbortController”。这是因为npm版本太低，没有这个AbortController。然后更新更高版本的npm，又提示“GLIBC_2.28' not found”。这个是因为Ubuntu版本太低，需要升级系统。原文章链接：https://carmencincotti.com/2023-02-13/resolve-glibc-228-not-found/
    



# 以下是目前已经部署在链上的合约：
Entrypoint:
[https://blockscan.com/address/0x0576a174d229e3cfa37253523e645a78a0c91b57](https://blockscan.com/address/0x0576a174d229e3cfa37253523e645a78a0c91b57)

SimpleAccountFactory:
[https://blockscan.com/address/0x09c58cf6be8e25560d479bd52b4417d15bca2845](https://blockscan.com/address/0x09c58cf6be8e25560d479bd52b4417d15bca2845)


polygon-mumbai:
    SoulWalletFactory:0xC544A5107d887c9df046Cd8C5fB9D61e7559c229
	    walletImpl:0x0F8065973c4F7AB41E739302152c5cB6aC7590BA			
			
                
polygon-mainnet:
	SoulWalletFactory:0x28D188e045528c6C29BD51AA6CF0D1a885720EA1
		walletImpl:0xc535620Bc1E7e0A5682A1A0b3D4878e8a32c0e81
			
                
arbitrum-mainnet:
	SoulWalletFactory:0x56F379758b6B52830D7dde019A43b2cBdec37c5E
		walletImpl:0x9f8241410c00a24bf2efe5bf42226645fb1f3839
			
polygon-mainnet:
	SimpleAccountFactory:0x09c58cf6be8e25560d479bd52b4417d15bca2845
    
polygon-mumbai:
	SimpleAccountFactory:0x09c58cf6be8e25560d479bd52b4417d15bca2845
    
arbitrum-mainnet:
	SimpleAccountFactory:0x09c58cf6be8e25560d479bd52b4417d15bca2845