PlaTrust 开荒 | 探路(一)

PlatTrust 开荒 | 探路

@LeQianLiang

记录一下开荒的过程,并用通俗的方式讲一下过程。

整个 SDK 中最重要的部分是 WalletLib 在最早的初始化时,需要先实例化 platrust.WalletLib ,然后执行 walletLib.getSetupCode(),在这个函数里,一共包含 7 个入参:

/**
 * 同步等待getSetupCode完成,并将返回值赋予initializer
 */
const initializer = await walletLib.getSetupCode(
    relayerManagerAddr,   // <address> EntryPoint Contract Address
    owners, // <[address]> owner Address List
    1,       // <number> threshold
    AddressZero,  // <address> to Address
    '0x',  // <string> wallet init execute data
    AddressZero,  // <string> fallbackHandler
    86400,      // <number> lockPerid (这里的时间单位是毫秒)
)
console.log("1. initializer:", initializer,'\n');

此处 AddressZero 在官网说明中未给出,应赋值:

const AddressZero = '0x0000000000000000000000000000000000000000';

完成此逻辑后,进入:

/**
 * 计算即将生成的钱包地址
 */
const walletAddress = await walletLib.calculateWalletAddress(
    walletLogic,  // <address> BonusWalletLogic Contract Address
    initializer,  // <string> initializer
    salt,     // <string> salt (Hex string)
    walletFactory  // <address> wallet Factory Address
);
console.log("2. walletAddress:", walletAddress,'\n');

从运行结果上看,initializer 的值是不变的,而计算生成钱包地址的关键因素,在于 salt

我们分别使用两个不同的 salt 作为参数值,结果如下图所示:

const salt = ethers.utils.formatBytes32String("A");
const salt = ethers.utils.formatBytes32String("B");

2 个赞

通过对照试验,得到以下结论:

owners salt wallet
不同 不同 不同
不同 相同 不同
相同 不同 不同
相同 相同 相同

通过执行:

const userOpHash = await activateOp.getUserOpHashFromContract(
        relayerManagerAddr,  // <address> EntryPoint Contract Address
        new ethers.providers.JsonRpcProvider( chainURL),  // ethers.providers
    );

console.log("6. userOpHash:", userOpHash,'\n');

能够获得

userOpHash = "0xde2e31d1f4721802059dd913553c6445da6015905bff9c26809058bcb7896b39"

但是此 Hash 在 PlatON 开发网络浏览器中查询无结果。这是为什么呢?难道此处的 Hash 是指对用户操作的一次哈希计算?

在执行:

const signedHash = platrust.packSignatureHash(userOpHash, SignatureMode.owner, 0, 0);

console.log("7. signedMsg: ", signedHash,'\n');

其中入参的后两个数值是表示签名有效期的起止时间,此处在文档中使用BigNumber作为数据类型,是填写UNIX时间的毫秒值还是填写秒值?如果是时间的话,应采用 Date 对象会更好吧?

由于在 src/utils/httpRequest.ts 中使用了 AbortController,因此建议 Node.js 请控制在 v15.0.0 以上。

参考链接:Node.js v15.x 新特性 — 控制器对象 AbortController - 掘金

官方文档中并未提及完整用例的环境配置,由于在 Node. js v18 以下的版本中,均未将 fetch 添加至全局环境,因此如果使用 Node.js v15.0.0 ~ Node.js 17.9.1,请通过

npm install node-fetch@2

为程序提供 fetch

至此,采用Node.js v18.16.0 执行CreateWallet 完成:

// import { ethers } from "ethers";
// import { Accounts } from "web3-eth-accounts";
// import { UserOpReceipt, BaseWalletLib, UserOperation, packSignatureHash, signMessage, encodeSignature } from 'bonus-wallet-js-sdk';
const platrust = require("platrust-wallet-js-sdk")
const ethers = require("ethers")
const AddressZero = '0x0000000000000000000000000000000000000000';
const SignatureMode = {
    owner : 0x0,
    guardians : 0x1,
    session : 0x2
}

async function main() {
    //组 1
    const pks = ['']
    let owners = ['']
    //组 2
    //const pks = ['']
    //let owners = ['']

    const chainURL = 'https://devnet2openapi2.platon.network/rpc'
    const bundleURL = 'https://testbundler.platon.network'

    const walletLib = new platrust.WalletLib();
    const walletLogic = '0x3b682b956E65b5F5b8150f75F2235f156A8F4b7B'
    const walletFactory = '0x97429FFFdE9223C92Cb00F66D8352B0642f70FA4' // wallet proxy factory contract address
    const relayerManagerAddr = '0xD7998fC16185cC619b0918028D9BBc77A844a880'
    const salt = ethers.utils.formatBytes32String("abc");

    /**
     * 同步等待getSetupCode完成,并将返回值赋予initializer
     */
    const initializer = await walletLib.getSetupCode(
        relayerManagerAddr,   // <address> EntryPoint Contract Address
        owners, // <[address]> owner Address List
        1,       // <number> threshold
        AddressZero,  // <address> to Address
        '0x',  // <string> wallet init execute data
        AddressZero,  // <string> fallbackHandler
        86400,      // <number> lockPerid
    )

    console.log("1. initializer:", initializer,'\n');
    /**
     * 计算即将生成的钱包地址
     */
    const walletAddress = await walletLib.calculateWalletAddress(
        walletLogic,  // <address> BonusWalletLogic Contract Address
        initializer,  // <string> initializer
        salt,     // <string> salt (Hex string)
        walletFactory  // <address> wallet Factory Address
    );
    console.log("2. walletAddress:", walletAddress,'\n');

    const initcode = walletLib.getInitCode(walletFactory, walletLogic, initializer, salt);

    console.log("3. initcode:",initcode,'\n');

    const activateOp = walletLib.activateWalletOp(
        walletLogic,  // <address> BonusWallet Logic Contract Address
        initializer,  // <string> initializer
        undefined,   // <bytes> paymasterAndData
        salt,     // <string> salt (Hex string)
        walletFactory,  // <address> Wallet factory Contract Address
        100,// <number> maxFeePerGas 100Gwei
        1000,// <number> maxPriorityFeePerGas 10Gwei
        5000000,
        500000,
        100000
    );

    console.log("4. activateOp:",activateOp,'\n');
    console.log("5. user op:", activateOp.toTuple(), '\n');
    
    const userOpHash = await activateOp.getUserOpHashFromContract(
        relayerManagerAddr,  // <address> EntryPoint Contract Address
        new ethers.providers.JsonRpcProvider( chainURL),  // ethers.providers
    );

    console.log("6. userOpHash:", userOpHash,'\n');

    const signedHash = platrust.packSignatureHash(userOpHash, SignatureMode.owner, 1690963200000, 1690977600000);

    console.log("7. signedMsg: ", signedHash,'\n');

    let sigs = '0x'
    for (var i = 0; i < pks.length; i++) {
        const sig = platrust.signMessage(signedHash, pks[i])
        sigs = ethers.utils.solidityPack(
            ['bytes', 'bytes'],
            [sigs, sig]
        )
    }
    console.log('8. sig: ', sigs,'\n');

    activateOp.signature = platrust.encodeSignature(SignatureMode.owner, sigs, 0, 0);
    console.log("9. signature: ", activateOp.signature,'\n');

    const bundler = new walletLib.Bundler(
        relayerManagerAddr,  // <address> EntryPoint Contract Address
        new ethers.providers.JsonRpcProvider(chainURL),
        bundleURL
    );

    console.log("10. bundler: ", bundler,'\n');

    const entryPoints = await bundler.platon_supportedEntryPoints()
    console.log('11. entry points: ', entryPoints,'\n');

    const validation = await bundler.simulateHandleOp(activateOp);
    console.log('11. validation: ', validation,'\n');

    const bundlerEvent = bundler.sendUserOperation(activateOp);
    console.log("11. bundlerEvent: ", bundlerEvent,'\n');

    bundlerEvent.on('error', (err) => {
        console.log("error: ", err);
    });
    bundlerEvent.on('send', async (userOpHash) => {
        console.log('send: ' + userOpHash);
    });
    bundlerEvent.on('receipt', (receipt) => {
        console.log('receipt: ' + JSON.stringify(receipt));
    });
    bundlerEvent.on('timeout', () => {
        console.log('timeout');
    });
}

main(); //创建钱包

但是会报告 AA23 reverted: wallet: signer not a owner

通过检查发现,可能是对授权签名的起止时间的数值使用错误导致AA23的发生,因此将所有时间修正为 0。执行后报告:

Error: missing revert data in call exception; Transaction reverted without a reason string

对于 AA13 错误:

错误的使用 AddressZero,例如替换成非 0x00 地址,则会导致 initcode failop or OGG

对于详细的 EIP-4337 的内容包括具体实现,PlatON并没有给出很明确地讲解与剖析,有兴趣的读者可以参见此博客:EIP-4337 (Account Abstraction Using Alt Mempool)

1 个赞

对于 AA21 错误:

应对生成的 Wallet 转入一定的 LAT,余额充足时不会出现此错误。

3 个赞

这里的userOpHash并不是交易hash,因此在区块链浏览器中并不能查询到,但是这个hash可以通过bundler提供的接口查询到对应的userOp信息

AddressZero在js sdk中有声明的

是有申明,但是在具体使用的时候,比如说我开个demo.js,直接放AddressZero是不行的,尽管我直接调用了整个platrust的包。

这可能和引入时的声明有关。可以看官网的示例,如果按照那个示例执行,会报错,AddressZero is not definded

是的,后来我也意识到这点,它和反事实地址类似,是把用户操作上传到捆绑器里面等待一次打包上传。

但是尽管我模拟验证无误,可是模拟执行时会报错的。说我的userOp是无效的。

你可以这么引用platrust.WalletLib.Defines.AddressZero

1 个赞

我看了下你的这个demo,第一个在platrust.packSignatureHash(userOpHash, SignatureMode.owner, 1690963200000, 1690977600000) 这行代码的后两个参数应该要和activateOp.signature = platrust.encodeSignature(SignatureMode.owner, sigs, 0, 0)这行代码后两个参数保持一致,具体这两个参数可以参考链接,第二个需要注意的点是walletLib.activateWalletOp里的maxFeePerGas和maxPriorityFeePerGas这两个参数取链上的gasPrice(可通过eth_gasPrice这个rpc请求获取)

1 个赞

这个Demo已经是过去式了,之后我把packSignatureHash关于时间的两个参数都设置为 0,和后面的encodeSignature保持一致。

另外第二个点的maxFeePerGas和maxPriorityFeePerGas我增加了一些数量,已经确保这两项是可以完整运行的。

主要是在执行:

const bundler = new walletLib.Bundler(
        relayerManagerAddr,  // <address> EntryPoint Contract Address
        new ethers.providers.JsonRpcProvider(chainURL),
        bundleURL
    );
    const entryPoints = await bundler.platon_supportedEntryPoints()
    console.log('entry points: ', entryPoints)

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

    const bundlerEvent = bundler.sendUserOperation(activateOp);
    bundlerEvent.on('error', (err: any) => {
        console.log("error: ", err);
    });
    bundlerEvent.on('send', async (userOpHash: string) => {
        console.log('send: ' + userOpHash);
    });
    bundlerEvent.on('receipt', (receipt: UserOperationReceipt) => {
        console.log('receipt: ' + JSON.stringify(receipt));
    });
    bundlerEvent.on('timeout', () => {
        console.log('timeout');
    });

sendUserOperation 这个函数遇到了错误。显示无效的 userOpHash。

后面应该是能打印出交易回执的吧

打不出来回执,一直停在无效的userOpHash