当前位置:   article > 正文

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

web3j

前言

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

二:使用独立的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
  • 1

2,更新yum

bash-4.2# yum update
  • 1

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

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

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
  • 1

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

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

接着到你指定的输出目录就能看到编译后的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;

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
    @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);


    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

补充: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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/415667
推荐阅读
相关标签
  

闽ICP备14008679号