如何使用Web3j解码以太坊交易的Input Data?
你之前直接把Input Data转成UTF-8字符串得到乱码,这很正常——以太坊交易的Input Data根本不是普通的文本,它是按照Solidity ABI规范编码的二进制数据,前4字节是函数签名的哈希值,后面跟着的是各个参数的编码结果,所以不能直接用UTF-8解码。
下面我给你两种实用的解码方案,你可以根据自己的情况选择:
方案一:已知合约ABI时(推荐)
如果能拿到目标合约的ABI(可以从Etherscan、合约代码仓库或者部署者那里获取),用Web3j的ABI解码工具就能轻松搞定:
步骤1:提取参数部分
Input Data的前4字节(也就是0x之后的前8个字符)是函数签名哈希,我们需要把这部分去掉,只保留后面的参数数据:
String inputData = tx.get().getInput(); // 截取参数部分:跳过0x和前4字节的签名哈希(共10个字符) String paramData = inputData.substring(10);
步骤2:根据ABI定义参数类型
假设你的合约函数签名对应的参数类型是(bytes, uint256, bytes, bytes)(对应你给出的Input Data结构),我们需要用Web3j的TypeReference来定义这些类型:
import org.web3j.abi.FunctionReturnDecoder; import org.web3j.abi.TypeReference; import org.web3j.abi.datatypes.DynamicBytes; import org.web3j.abi.datatypes.Uint256; import java.util.Arrays; import java.util.List; // 对应函数的参数类型,顺序要和ABI里的一致 List<TypeReference<?>> paramTypes = Arrays.asList( new TypeReference<DynamicBytes>() {}, new TypeReference<Uint256>() {}, new TypeReference<DynamicBytes>() {}, new TypeReference<DynamicBytes>() {} );
步骤3:解码并输出结果
用FunctionReturnDecoder解码参数,然后就能拿到可读的结果了:
List<Type> decodedParams = FunctionReturnDecoder.decode(paramData, paramTypes); // 遍历输出每个解码后的参数 for (int i = 0; i < decodedParams.size(); i++) { System.out.printf("参数%d: %s%n", i+1, decodedParams.get(i).getValue()); }
进阶:用Web3j生成合约Java类
如果合约ABI完整,你还可以用Web3j的代码生成工具自动生成合约的Java类,这样解码会更简单:
- 安装Web3j命令行工具
- 执行生成命令:
web3j generate solidity -a 你的合约ABI文件.json -o 输出目录 -p 你的包名
生成的类里会包含解码Input Data的方法,直接调用即可,不用手动定义参数类型。
方案二:未知合约ABI时
如果不知道合约ABI,你可以先通过前4字节的函数签名哈希(0x070ec83b)去识别对应的函数签名——可以用以太坊签名数据库查询,或者本地用Solidity编译器计算。拿到函数签名后,再按照方案一的步骤定义参数类型进行解码。
为什么之前的方法不行?
你之前把十六进制转成字节后直接转UTF-8字符串,相当于把ABI编码的二进制数据当成普通文本处理,而ABI编码的参数(比如uint256、动态字节数组)根本不是UTF-8编码的文本,所以必然会得到乱码。
内容的提问来源于stack exchange,提问作者Nicolas Flores Muñoz

