V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Joker123456789
V2EX  ›  以太坊

大家好,我开发了一个扫块的 工具包,可以用来方便的监听 交易动态

  •  
  •   Joker123456789 · 2022-11-05 17:49:06 +08:00 · 1651 次点击
    这是一个创建于 509 天前的主题,其中的信息可能已经有所发展或是发生改变。

    开发语言是 java ,底层依赖了 Web3J , 废话不多说,直接上示例

    引入依赖

    <dependency>
        <groupId>com.github.yuyenews</groupId>
        <artifactId>Magician-web3</artifactId>
        <version>1.0.0</version>
    </dependency>
    
    <!-- This is the logging package, you must have it or the console will not see anything, any logging package that can bridge with slf4j is supported -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jdk14</artifactId>
        <version>1.7.12</version>
    </dependency>
    

    创建监听器

    监听器 可以创建多个,根据你的需求 分别设置监听条件

    /**
     * 创建一个类,实现 EthMonitorEvent 接口 即可
     */
    public class EventDemo implements EthMonitorEvent {
    
        /**
         * 筛选条件,如果遇到了符合条件的交易,会自动触发 call 方法
         * 这些条件都是 并且的关系,必须要同时满足才行
         * 如果不想根据某个条件筛选,直接不给那个条件设置值就好了
         * 这个方法如果不实现,或者返回 null , 那么就代表监听任意交易
         */
        @Override
        public EthMonitorFilter ethMonitorFilter() {
            return EthMonitorFilter.builder()
                    .setFromAddress("0x131231249813d334C58f2757037F68E2963C4crc") // 筛选 fromAddress 发送的交易
                    .setToAddress("0x552115849813d334C58f2757037F68E2963C4c5e") // 筛选 toAddress 或 合约地址 收到的交易
                    .setMinValue(BigInteger.valueOf(1)) // 筛选发送的主链币数量 >= minValue 的交易
                    .setMaxValue(BigInteger.valueOf(10)) // 筛选发送的主链币数量 <= maxValue 的交易
                    .setFunctionCode("0xasdas123"); // 筛选调用合约内 某方法 的交易
        }
    
        /**
         * 如果遇到了符合上面条件的交易,就会触发这个方法
         * transactionModel.getEthTransactionModel() 是一个交易对象,内部包含 hash ,value ,from ,to 等 所有的数据
         */
        @Override
        public void call(TransactionModel transactionModel) {
            String template = "EventOne 扫描到了, hash:{0}, from:{1}, to: {2}, input: {3}";
            template = template.replace("{0}", transactionModel.getEthTransactionModel().getBlockHash());
            template = template.replace("{1}", transactionModel.getEthTransactionModel().getFrom());
            template = template.replace("{2}", transactionModel.getEthTransactionModel().getTo());
            template = template.replace("{3}", transactionModel.getEthTransactionModel().getInput());
    
            System.out.println(template);
        }
    }
    
    

    开启一个扫块任务

    
    // 初始化线程池,核心线程数必须 >= 扫块的任务数量,建议等于扫块的任务数量
    EventThreadPool.init(1);
    
    // 开启一个扫块任务,如果你想扫描多个链,那么直接拷贝这段代码,并修改配置即可
    MagicianBlockchainScan.create()
            .setRpcUrl("https://data-seed-prebsc-1-s1.binance.org:8545/") // 节点的 RPC 地址
            .setChainType(ChainType.ETH) // 要扫描的链(如果设置成 ETH ,那么可以扫描 BSC, POLYGAN 等其他任意 以太坊标准的链)
            .setScanPeriod(5000) // 每轮扫描的间隔
            .setScanSize(1000) // 每轮扫描的块数
            .setBeginBlockNumber(BigInteger.valueOf(24318610)) // 从哪个块高开始扫描
            .addEthMonitorEvent(new EventOne()) // 添加 监听事件
            .addEthMonitorEvent(new EventTwo()) // 添加 监听事件
            .addEthMonitorEvent(new EventThree()) // 添加 监听事件
            .start();
    
    // TODO 暂时不支持 SOL 和 TRON , 正在开发中......
    
    

    Web3J 扩展

    在 Web3j 的基础上进行了二次封装,扩展了几个基础的方法,可以在一定程度上减轻开发者的工作量

    主链币查询以及转账

    String privateKey = ""; // 私钥
    Web3j web3j = Web3j.build(new HttpService("https://data-seed-prebsc-1-s1.binance.org:8545/")); // 链的 RPC 地址
    
    EthHelper ethHelper =  MagicianWeb3.getEthBuilder().getEth(web3j, privateKey);
    
    // 余额查询
    BigInteger balance = ethHelper.balanceOf(fromAddress);
    
    // 转账
    TransactionReceipt transactionReceipt = ethHelper.transfer(
                toAddress,
                BigDecimal.valueOf(1),
                Convert.Unit.ETHER
    );
    

    InputData 编解码

    EthAbiCodec ethAbiCodec = MagicianWeb3.getEthBuilder().getEthAbiCodec();
    
    // 编码
    String inputData = ethAbiCodec.getInputData(
                "transfer", // 方法名
                new Address(toAddress), // 参数 1
                new Uint256(new BigInteger("1000000000000000000")) // 参数 2 ,如果还有其他参数,可以继续传入下一个
        );
    
    // 解码
    List<Type> result = ethAbiCodec.decoderInputData(
                "0x" + inputData.substring(10), // 去除方法签名的 inputData
                new TypeReference<Address>() {}, // 被编码的方法的参数 1 类型
                new TypeReference<Uint256>() {} // 被编码的方法的参数 2 类型, 如果还有其他参数,可以继续传入下一个
        );
    
    for(Type type : result){
        System.out.println(type.getValue());
    }
    
    // 获取方法签名,其实就是 inputData 的前十位
    String functionCode = ethAbiCodec.getFunAbiCode(
                "transfer", // 方法名
                new Address(toAddress), // 参数 1 ,值随意传,反正我们要的方法签名,不是完整的 inputData
                new Uint256(new BigInteger("1000000000000000000")) // 参数 2 ,值随意传,反正我们要的方法签名,不是完整的 inputData ,如果还有其他参数,可以继续传入下一个
        );
       
    

    合约查询 以及 写入

    String privateKey = ""; // 私钥
    Web3j web3j = Web3j.build(new HttpService("https://data-seed-prebsc-1-s1.binance.org:8545/")); // 链的 RPC 地址
    
    EthContract ethContract = MagicianWeb3.getEthBuilder().getEthContract(web3j, fromAddressPrivateKey);
    EthAbiCodec ethAbiCodec = MagicianWeb3.getEthBuilder().getEthAbiCodec();
    
    // 查询
    List<Type> result = ethContract.select(
                contractAddress, // 合约地址
                ethAbiCodec.getInputData(
                        "balanceOf", // 要调用的方法名称
                        new Address(toAddress) // 方法的参数,如果有多个,可以继续传入下一个参数
                ),  // 要调用的方法的 inputData
                new TypeReference<Uint256>() {} // 方法的返回类型,如果有多个返回值,可以继续传入下一个参数
            );
    
    // 往合约里写入数据
    // gasPrice ,gasLimit 两个参数,如果想用默认值可以不传,或者传 null
    // 如果不传的话,两个参数都必须不传,要传就一起传, 如果设置为 null 的话,可以一个为 null ,一个有值
    SendResultModel sendResultModel = ethContract.sendRawTransaction(
                        fromAddress, // 调用者的地址
                        contractAddress, // 合约地址
                        new BigInteger("1200000"), // gasPrice ,如果想用默认值 可以直接传 null ,或者不传这个参数
                        new BigInteger("800000"), // gasLimit ,如果想用默认值 可以直接传 null ,或者不传这个参数
                        ethAbiCodec.getInputData(
                                "transfer", // 要调用的方法名称
                                new Address(toAddress), // 方法的参数,如果有多个,可以继续传入下一个参数
                                new Uint256(new BigInteger("1000000000000000000")) // 方法的参数,如果有多个,可以继续传入下一个参数
                        ) // 要调用的方法的 inputData
                );
    
    sendResultModel.getEthSendTransaction(); // 发送交易后的结果
    sendResultModel.getEthGetTransactionReceipt(); // 交易成功上链后的结果
    

    github 地址:https://github.com/Magician-blockchain/Magician-web3

    第 1 条附言  ·  2022-11-06 16:03:10 +08:00
    为了更形象的解释这个工具包,我举个例子,比如:

    我如果就是只想 在某个地址 收到了主链币时,程序可以收到这条交易信息的所有字段。 用我这个工具包来做的话,真正需要手写的代码 只有半行而已(不包括 收到通知后的业务操作,因为这个是逃不掉的,用什么工具开发 都得写)。

    如果 在上面那个场景下 再加一个场景,当某个地址 收到或者发送的主链币数量在某个区间时,程序也能收到对应的交易信息,那么什么都不用改,只需要再写 半行代码而已。 而且这半行 跟 那半行 是 隔离的,完全的解耦。

    如果 这两个场景,你需要下掉一个,那只需要直接删 或者注释即可。

    如果还有第三个场景,那就再写半行,最多一行。

    注:我上面说的半行,一行,指的是需要手写的代码,不是总代码,但是总代码也不多,每个场景对应一个实现类,里面实现两个方法而已。

    作为开发者,只需要关注 自己需要监听什么样的事件,其他的都不需要去耗费精力。 对于写入合约的交易,可能需要开发者 自己进行一次二筛,比如去查 log 来判定 本次交易是否有效,解析 inputData 来获取 更详细的信息,进行判断等。
    2 条回复    2022-11-05 18:19:25 +08:00
    rimutuyuan
        1
    rimutuyuan  
       2022-11-05 17:53:26 +08:00
    token 交易要监听 log ,只关注 data 会出现 revert 或者恶意消息这种情况
    Joker123456789
        2
    Joker123456789  
    OP
       2022-11-05 18:19:25 +08:00
    @rimutuyuan 是的,所以需要二筛, 合约千变万化,光是 ERCXX 这种标准合约就很多了,再加上其他的一些 dapp ,所以没办法统一处理,只能交给开发者自己二筛了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2731 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 15:29 · PVG 23:29 · LAX 08:29 · JFK 11:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.