【java应用web3】web3j中,顺利编译智能合约sol为java文件的正确方式。并在java中调用智能合约

前言

java也有对web3的支持,其依赖叫做web3j。
web3j对于智能合约的调用与交互,是通过将其智能合约的sol文件编译成对应的java文件来实现的。
web3j对于sol文件的编译有两种,
一:在IDE项目中通过Maven/Gradle插件编译
maven配置如下:

    <build>
        <plugins>

            <!-- web3j插件。该插件会将.sol格式的solidity语法代码生成为对应语法的java文件。 -->
            <!-- 参考:https://cloud.tencent.com/developer/article/1373951 -->
            <plugin>
                <groupId>org.web3j</groupId>
                <artifactId>web3j-maven-plugin</artifactId>
                <version>4.8.7</version>
                <configuration>

                    <!-- 指定.sol文件所在的资源目录,插件将会扫描该目录。 -->
                    <soliditySourceFiles>
                        <directory>src/main/resources/solidity</directory>
                        <includes>
                            <!-- 指定扫描以.sol结尾的文件格式 -->
                            <include>**/*.sol</include>
                        </includes>
                    </soliditySourceFiles>

                    <!-- 指定将.sol文件编译为.java文件后的输出路径 -->
                    <outputDirectory>
                        <java>src/main/java/com/nb/common/contract/generated/java</java>
                        <bin>src/main/java/com/nb/common/contract/generated/bin</bin>
                        <abi>src/main/java/com/nb/common/contract/generated/abi</abi>
                    </outputDirectory>

                </configuration>
            </plugin>


        </plugins>
    </build>

二:使用独立的Web3j CLI工具编译。

问题背景

一开始,我是使用第一种方式,也就是maven插件来编译sol文件的,对于结构简单的智能合约sol文件来讲,是比较顺利的。
但是对于博饼路由器(PancakeRouter)这类一个代码文件中具有多个合约/接口并存在继承关系的sol合约文件来讲,web3j的maven编译插件并不能很好的支持它,很多时候要么是内部各个接口的solidity版本不同导致编译不了,要么就是报note that nightly builds are considered to be strictly less than the released version这类错误。
那么,需要做的,就是能够顺利编译这类合约文件。

解决思路

后来我又查看了一遍web3j的官方文档,发现官方有提供一个叫Web3j CLI工具,经过尝试,发现使用这个工具可以完美解决问题,成功的将博饼路由器(PancakeRouter)合约编译为java文件。
Web3j CLI的工具最好在centos系统下使用,但需要配备以下二个环境
第一个是solc环境,通过npm安装,
第二个是jdk环境。

解决过程

我的Web3j CLI是安装在docker的nojdes镜像容器实例,因为这样可以直接使用npm。

1,进入nodejs容器实例

bash-4.2# docker exec -it --user root nodejs bash

2,更新yum

bash-4.2# yum update

3,全局安装指定为0.6.6版本的solc
0.6.6版本不仅为solc版本,也代表所支持的solidity智能合约版本

bash-4.2# sudo npm install -g solc@0.6.6

4,安装jdk环境

参考 https://blog.csdn.net/chinatopno1/article/details/104935100

5,安装Web3j CLI

bash-4.2# curl -L get.web3j.io | sh && source ~/.web3j/source.sh

6,查看版本,以确认是否安装成功

bash-4.2# web3j -v
              _      _____ _ 
             | |    |____ (_)
__      _____| |__      / /_ 
\ \ /\ / / _ \ '_ \     \ \ |
 \ V  V /  __/ |_) |.___/ / |
  \_/\_/ \___|_.__/ \____/| |
                         _/ |
                        |__/ 
by Web3Labs
Version: 1.4.1
Build timestamp: 2021-02-16 20:28:33.742 UTC

7,先编译.sol文件为.bin和.abi文件

# PancakeRouter.sol为文件名   pancakerouter-builded/为输出目录
bash-4.2# solcjs PancakeRouter.sol --bin --abi --optimize -o pancakerouter-builded/

# 查看编译结果
bash-4.2# ls
PancakeRouter_sol_IERC20.abi            PancakeRouter_sol_IWETH.abi
PancakeRouter_sol_IERC20.bin            PancakeRouter_sol_IWETH.bin
PancakeRouter_sol_IPancakeFactory.abi   PancakeRouter_sol_PancakeLibrary.abi
PancakeRouter_sol_IPancakeFactory.bin   PancakeRouter_sol_PancakeLibrary.bin
PancakeRouter_sol_IPancakePair.abi      PancakeRouter_sol_PancakeRouter.abi
PancakeRouter_sol_IPancakePair.bin      PancakeRouter_sol_PancakeRouter.bin
PancakeRouter_sol_IPancakeRouter01.abi  PancakeRouter_sol_SafeMath.abi
PancakeRouter_sol_IPancakeRouter01.bin  PancakeRouter_sol_SafeMath.bin
PancakeRouter_sol_IPancakeRouter02.abi  PancakeRouter_sol_TransferHelper.abi
PancakeRouter_sol_IPancakeRouter02.bin  PancakeRouter_sol_TransferHelper.bin

8,根据生成后的.abi和.bin文件,编译为.java文件

# 格式:web3j generate solidity -b <文件名>.bin -a <文件名>.abi -o <输出目录> -p <包路径>

bash-4.2# web3j generate solidity -b pancakerouter-builded/PancakeRouter_sol_PancakeRouter.bin -a pancakerouter-builded/PancakeRouter_sol_PancakeRouter.abi -o pancakerouter-builded/ -p com.nb.common.contract.generated.java.org.web3j.model

              _      _____ _ 
             | |    |____ (_)
__      _____| |__      / /_ 
\ \ /\ / / _ \ '_ \     \ \ |
 \ V  V /  __/ |_) |.___/ / |
  \_/\_/ \___|_.__/ \____/| |
                         _/ |
                        |__/ 
by Web3Labs
Generating com.nb.common.contract.generated.java.org.web3j.model.PancakeRouter_sol_PancakeRouter ... File written to pancakerouter-builded


接着到你指定的输出目录就能看到编译后的java文件了。

9.这个时候我们把编译为java文件的智能合约代码添加项目工程中,通过web3j可以直接来实现交互了。
在这里插入图片描述
比如我这里要同pancakerouter合约交互,将其一些需要交互的过程封装成一个类,方便后续的开发。

package com.nb.common.contract.datainteraction;

import com.nb.common.contract.generated.java.org.web3j.model.PancakeRouterSolPancakeRouter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.RemoteFunctionCall;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.tx.gas.DefaultGasProvider;
import org.web3j.tx.gas.StaticGasProvider;
import org.web3j.utils.Convert;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

public class PancakeSwapTransaction {

    private final static Logger logger = LoggerFactory.getLogger(PancakeSwapTransaction.class);

    private Web3j web3j;

    private Credentials walletCredentials;

    //private String pancakeRouterAddress;

    private static final String defaultPancakeRouterAddress = "0x10ED43C718714eb63d5aA57B78B54704E256024E";

    //private String netTokenAddress;

    private static final String defaultNetTokenAddress = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c";

    private static final String usdtAddress = "0x55d398326f99059fF775485246999027B3197955";

    private static final String defaultGasPriceTheBuy = "5000000000";
    private static final String defaultGasLimitTheBuy = "269380";

    private static final String defaultGasPriceTheSell = "5000000000";
    private static final String defaultGasLimitTheSell = "516087";


    private PancakeRouterSolPancakeRouter pancakeRouter;



    @Deprecated
    public PancakeSwapTransaction() {
    }


    public PancakeSwapTransaction(Web3j web3j, Credentials walletCredentials) {
        this.web3j = web3j;
        this.walletCredentials = walletCredentials;
        this.pancakeRouter = PancakeRouterSolPancakeRouter.load(defaultPancakeRouterAddress, web3j, walletCredentials, new DefaultGasProvider());
    }


    public PancakeRouterSolPancakeRouter getPancakeRouterInstance(){
        return this.pancakeRouter;
    }


    public TransactionReceipt buy(String contractAddress,String usdtAmount) throws Exception {
       return buy(contractAddress, usdtAmount, null,null );
    }


    public TransactionReceipt buy(String contractAddress,String usdtAmount,String gasPrice,String gasLimit) throws Exception {

        BigDecimal amountInput = Convert.toWei(usdtAmount, Convert.Unit.ETHER);
        BigDecimal amountOutput = new BigDecimal("1");

        List<String> path = new ArrayList<>();
        path.add(usdtAddress);
        path.add(defaultNetTokenAddress);
        path.add(contractAddress);

        EthBlock.Block block = this.web3j.ethGetBlockByNumber(DefaultBlockParameterName.LATEST, false).send().getBlock();
        logger.info("current number:"+block.getNumber());
        BigInteger timestampUnix = block.getTimestamp();
        logger.info("time add Before:"+timestampUnix);

       
        timestampUnix = timestampUnix.add(new BigInteger("1000"));
        logger.info("time add After:"+timestampUnix);


        StaticGasProvider staticGasProvider = new StaticGasProvider(StringUtils.isEmpty(gasPrice) ? new BigInteger(defaultGasPriceTheBuy) : new BigInteger(gasPrice) ,
                StringUtils.isEmpty(gasLimit) ? new BigInteger(defaultGasLimitTheBuy) : new BigInteger(gasLimit));


        this.pancakeRouter.setGasProvider(staticGasProvider);




        RemoteFunctionCall<TransactionReceipt> transactionReceiptRemoteFunctionCall = this.pancakeRouter.swapExactTokensForTokens(amountInput.toBigInteger(), amountOutput.toBigInteger(), path, walletCredentials.getAddress(), timestampUnix);
        TransactionReceipt buyResult = transactionReceiptRemoteFunctionCall.send();
        return buyResult;

    }


    public TransactionReceipt sell(String contractAddress,String tokenAmount) throws Exception {
        return sell(contractAddress, tokenAmount, null,null );
    }


    public TransactionReceipt sell(String contractAddress,String tokenAmount,String gasPrice,String gasLimit) throws Exception{

        BigDecimal amountInput = new BigDecimal(tokenAmount);

        BigDecimal amountOutput = new BigDecimal("1");

        List<String> path = new ArrayList<>();
        path.add(contractAddress);
        path.add(defaultNetTokenAddress);
        path.add(usdtAddress);

        EthBlock.Block block = this.web3j.ethGetBlockByNumber(DefaultBlockParameterName.LATEST, false).send().getBlock();
        logger.info("current number:"+block.getNumber());
        BigInteger timestampUnix = block.getTimestamp();
        logger.info("time add Before:"+timestampUnix);


        timestampUnix = timestampUnix.add(new BigInteger("1000"));
        logger.info("time add After:"+timestampUnix);

        StaticGasProvider staticGasProvider = new StaticGasProvider(StringUtils.isEmpty(gasPrice) ? new BigInteger(defaultGasPriceTheSell) : new BigInteger(gasPrice) ,
                StringUtils.isEmpty(gasLimit) ? new BigInteger(defaultGasLimitTheSell) : new BigInteger(gasLimit));
        this.pancakeRouter.setGasProvider(staticGasProvider);

        
        TransactionReceipt sellResult = this.pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(amountInput.toBigInteger(), amountOutput.toBigInteger(), path, walletCredentials.getAddress(), timestampUnix).send();
        return sellResult;

    }
}

    @Test
    public void PancakeTransactionTest() throws Exception{

        Web3j web3jObj = Web3j.build(new HttpService("http://链上地址/"));
        Credentials wallet = WalletUtils.loadJsonCredentials("自己的密码", "json文");

        PancakeSwapTransaction pancakeSwapTransaction = new PancakeSwapTransaction(web3jObj, wallet);
        TransactionReceipt buy = pancakeSwapTransaction.buy("0x地址", "2");
        System.out.println(buy);
        String tokenBalance = balanceOf("0x地址", wallet.getAddress());
        TransactionReceipt sell = pancakeSwapTransaction.sell("0x地址", tokenBalance);
        System.out.println(sell);


    }

补充:solc无法将sol文件转换为abi/bin的问题

今天尝试将uniswapv2Router2合约编译为java文件,但是中间使用solc转换为abi和bin文件时报错"solcjs ParserError: Source not found:xxxx File import callback not supporte"
尝试了一些解决办法,发现还是不太容易通过solc来转换,但是其中一篇文章中(点我查看)则是介绍,可以用网页版IDE remix 来将其转换为abi/和bin文件的,随后再通过web3j文件将其abi和bin转换为java文件。本人通过此办法解决。

参考:https://docs.web3j.io/4.8.7/quickstart/
https://help.aliyun.com/document_detail/102372.html
http://www.blogjava.net/waterjava/archive/2019/07/08/434107.html
https://blog.csdn.net/chinatopno1/article/details/104935100
https://blog.csdn.net/cangyuemis/article/details/111928457

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java应用程序编写完成后,会通过Java编译器将源代码转换为字节码文件,其文件扩展名为.class。这些字节码文件包含了Java虚拟机可以理解和执行的指令集合。 字节码文件是一种文件,它与平台无关,可以在任何支持Java虚拟机的系统运行。Java虚拟机会将字节码文件加载到内存,逐行解释执行其的指令。 通过将Java程序编译为字节码文件,可以实现Java的跨平台特性。无论是在Windows、Linux还是Mac等不同的操作系统上,只要有Java虚拟机,就可以运行相同的字节码文件。 扩展名为.class的文件一般不需要手动编辑,因为它是编译后的程序文件,包含机器可读的字节码指令,无法直观地进行修改。要对Java程序进行修改或改进,需要先修改源代码,然后重新编译生成新的.class文件。 在编写好的Java应用程序,可以通过编辑器来查看和编辑源代码文件。常见的Java编辑器有Eclipse、IntelliJ IDEA和Visual Studio Code等。这些编辑器提供了代码高亮、语法检查、代码提示等功能,方便开发人员编写、调试和维护Java程序。 总之,扩展名为.class的文件是通过Java编译器将Java应用程序源代码编译生成的字节码文件,它可以在任何支持Java虚拟机的系统运行,实现了Java的跨平台特性。编辑器则是用来编写、编辑和维护Java程序源代码的工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值