java也有对web3的支持,其依赖叫做web3j。
web3j对于智能合约的调用与交互,是通过将其智能合约的sol文件编译成对应的java文件来实现的。
web3j对于sol文件的编译有两种,
一:在IDE项目中通过Maven/Gradle插件编译
maven配置如下:
<!-- web3j插件。该插件会将.sol格式的solidity语法代码生成为对应语法的java文件。 -->
<!-- 参考:https://cloud.tencent.com/developer/article/1373951 -->
org.web3j</groupId>
web3j-maven-plugin</artifactId>
4.8.7</version>
<!-- 指定.sol文件所在的资源目录,插件将会扫描该目录。 -->
src/main/resources/solidity</directory>
<!-- 指定扫描以.sol结尾的文件格式 -->
**/*.sol</include>
</includes>
</soliditySourceFiles>
<!-- 指定将.sol文件编译为.java文件后的输出路径 -->
src/main/java/com/nb/common/contract/generated/java</java>
src/main/java/com/nb/common/contract/generated/bin</bin>
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 [email protected]
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);
}
今天尝试将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