Java与智能合约交互(Web3j)- write函数

说在前头

Web3是一种新兴的网络概念,由于某些原因导致我们能够接触到的相关技术知识实在有限,每当我遇见技术瓶颈总是不能找到充足的资料,这也让我萌生了填补这片空白知识的冲动。“Hello Web3” 这个专栏会尽力将我掌握的web3 知识分享给大家。如果分享的知识能帮助到大家,希望能够 关注点赞 支持作者!
本人已在github上发布Web3j工具,欢迎使用和star

Java与智能合约交互(Web3j)

之所以选择利用java与智能合约进行交互,完全是因为本人只会Java,并且Java是世界上最好的语言。

能干什么

  • 监控合约状态,读取合约的关键参数,可作为后台数据源。
  • 转账、授权等基础交互。
  • 实现例如抢购、提挖买等复杂交互。

代码分享

  1. 引入依赖
    <dependency>
        <groupId>org.web3j</groupId>
        <artifactId>core</artifactId>
        <version>5.0.0</version>
    </dependency>
  1. 新建Web3j对象
Web3j web3 = Web3j.build(new HttpService(rpcUrl));
  • rpcUrl变量是区块链网络节点的url链接,这些节点会提供很多标准的api方法通过该url进行调用,web3j模块就是在此api上进行封装。
  • 不同网络的rpcUrl可以在对应的区块链浏览器api文档上找到,百度关键字也很容易获取。
  1. 查询当前网络的gasPrice
// 获取gasPrice方法
BigInteger gasPrice = web3.ethGasPrice().send().getGasPrice();

/**
 * 获取当期的gasPrice,如果超过最大的限制,取最大限制
 *
 * @return 在区间内的gasPrice
 * @throws IOException 与节点交互出现异常
 */
public BigInteger getGasPriceWithLimit() throws IOException {
    
    
    BigInteger gasPrice = web3.ethGasPrice().send().getGasPrice();
    log.info("Gas price: {} Gwei, Min gas price: {} Gwei, Max gas price: {} Gwei",
    Convert.fromWei(String.valueOf(gasPrice), Convert.Unit.GWEI),
    Convert.fromWei(String.valueOf(minGasPrice), Convert.Unit.GWEI),
    Convert.fromWei(String.valueOf(maxGasPrice), Convert.Unit.GWEI));
    // 超过最大限制返回最大的gasPrice
    if (maxGasPrice.compareTo(gasPrice) < 0) {
    
    
       return maxGasPrice;
    }
    // 小于最小的限制返回最小的gasPrice
    if (minGasPrice.compareTo(gasPrice) > 0) {
    
    
       return minGasPrice;
    }
       return gasPrice;
}
  • 注意,该方法会获取近几个区块的gasPrice均值,可能偏高。
  • 参考作者的getGasPriceWithLimit方法,如果你不是抢购之类紧急的需求,建议不要直接使用这个方法的返回值作为gasPrice,不然亏哭你!
  1. 查询当前账户交易笔数用于作为交易nonce
	/**
     * 获取交易数量
     *
     * @return 账户交易次数
     * @throws IOException 与节点交互失败
     */
    public BigInteger getTransactionCount() throws IOException {
    
    
        EthGetTransactionCount ethGetTransactionCount = web3.ethGetTransactionCount(
                ownerAddress, DefaultBlockParameterName.LATEST).send();
        return ethGetTransactionCount.getTransactionCount();
    }
  • 以太坊虚拟机用这个nonce字段作为顺序来处理你的多个交易,默认一般nonce取账户的交易笔数
  • 本次交易nonce比上笔成功的交易还小的话会导致交易失败
  1. 查询当前区块链网络的链的chainId
long chainId = web3.ethChainId().send().getChainId().longValue();
  • 在以太坊经典从以太坊分叉出来后,为了防止双花攻击,Chain ID在EIP155被引入。
  • 通过在签名信息中加入Chain ID, 避免一个交易在签名之后被重复在不同的链上提交。
  1. 导入私钥(代码中的私钥切勿传到github公有仓库!!!)
Credentials credentials = Credentials.create(privateKey);
// 私钥对应的地址
String address = credentials.getAddress();
  1. 估算、签名、发送交易数据
    /**
     * 与合约交互
     *
     * @param contractAddress 交互合约地址
     * @param functionName    交互函数名称
     * @param value           携带的eth数量(单位Ether)
     * @param input           输入参数 eg:Arrays.asList(new Address("0x6dF655480F465DC36347a5616E875D155804F0c5"), new Uint256(10000000));
     * @param output          输出参数类型 eg: Arrays.asList(new TypeReference<Bool>(){});
     *                        类型映射关系
     *                        boolean - bool
     *                        BigInteger - uint/int
     *                        byte[] - bytes
     *                        String - string and address types
     *                        List - dynamic/static array
     *                        T - struct/tuple types
     * @return 交易hash
     * @throws Exception 与节点交互出现异常
     */
    public String writeContract(String contractAddress, String functionName, String value, List<Type> input, List<TypeReference<?>> output) throws Exception {
    
    
        // 转换value的单位
        BigInteger valueWei = Convert.toWei(value, Convert.Unit.ETHER).toBigInteger();
        // 生成需要调用函数的data
        Function function = new Function(functionName, input, output);
        String data = FunctionEncoder.encode(function);
        // 估算gasLimit
        BigInteger gasLimit = estimateGasLimit(contractAddress, data, valueWei);
        // 获取gasPrice
        BigInteger gasPrice = getGasPriceWithLimit();
        // 获取chainId
        long chainId = web3.ethChainId().send().getChainId().longValue();
        // 正式请求
        RawTransaction rawTransaction = RawTransaction.createTransaction(getNonce(), gasPrice, gasLimit, contractAddress, valueWei, data);
        // 签名数据
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);
        String hexValue = Numeric.toHexString(signedMessage);
        // 发送数据
        EthSendTransaction response = web3.ethSendRawTransaction(hexValue).send();
        // 查看是否有错误
        if (response.hasError()) {
    
    
            throw new Exception("trade hash: " + response.getTransactionHash() +
                    "\nerror: " + response.getError().getMessage());
        }
        log.info("function: {} data: {}", functionName, data);
        log.info("Gas fee: {} ETH", Convert.fromWei(String.valueOf(gasLimit.multiply(gasPrice)), Convert.Unit.ETHER));
        log.info("Trade Hash: {}", response.getTransactionHash());
        return response.getTransactionHash();
    }
    
    /**
     * 估算GasLimit
     *
     * @param to    发送的地址
     * @param data  发送的数据
     * @param value 携带的eth数量(单位wei)
     * @return GasLimit
     * @throws Exception 与节点交互失败
     */
    public BigInteger estimateGasLimit(String to, String data, BigInteger value) throws Exception {
    
    
        if (gasLimit.intValue() != 0) {
    
    
            return gasLimit;
        }
        Transaction testTransaction = Transaction.createFunctionCallTransaction(ownerAddress, null, null, null, to, value, data);
        EthEstimateGas response = web3.ethEstimateGas(testTransaction).send();
        // 查看是否有错误
        if (response.hasError()) {
    
    
            throw new Exception("error: " + response.getError().getMessage());
        }
        return response.getAmountUsed();
    }
  • writeContract函数的相关参数说明请参考上一篇文章《read函数》
  • estimateGasLimit函数用于计算本次执行的交易需要耗费的Gas数量,实际就是把你的交易数据传到链上模拟执行函数但不提交结果,将模拟执行花费的Gas作为请求结果返回
  • 当拿到交易所需的所有数据,需要用私钥对数据进行签名操作方可执行
  1. 调用写函数执行转账操作

    /**
     * 转账操作
     *
     * @param contractAddress 交互合约地址
     * @param recipient       接收转账地址
     * @param amount          转账数量
     * @return 交易hash
     * @throws Exception 与节点交互失败
     */
    public String transfer(String contractAddress, String recipient, String amount) throws Exception {
    
    
        List input = Arrays.asList(new Address(recipient)
                , new Uint256(new BigInteger(amount, 10)));
        List output = Arrays.asList(new TypeReference<Bool>() {
    
    
        });
        return writeContract(contractAddress, "transfer", input, output);
    }

掌握这些知识点最好的方法是自己将代码跑起来,去链上获取你想获取的信息

欢迎关注本专栏,作者知无不言~

猜你喜欢

转载自blog.csdn.net/weixin_43855305/article/details/124633724
今日推荐