以太坊 java开发依赖
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>3.5.0</version>
</dependency>
连接到以太坊同步节点
private String gethURL = "http://localhost:8545";
private void connectGeth() {
// --rpcport value HTTP-RPC服务器监听端口(默认值:8545)
this.web3j = Admin.build(new HttpService(gethURL));
if(null==web3j) {
System.out.println("connectGeth error");
}
}
创建账户,需要输入密码,返回账户地址 0x00000...
public String addAccount(String password) {
// walletId:$|chainId:$|account:$
if (web3j == null)
connectGeth();
String account = "";
try {
String fileName = WalletUtils.generateNewWalletFile(password, new File(keystorePath));
Credentials credentials = WalletUtils.loadCredentials(password, keystorePath + "/" + fileName);
account = credentials.getAddress();
} catch (InvalidAlgorithmParameterException e1) {
e1.printStackTrace();
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (NoSuchProviderException e1) {
e1.printStackTrace();
} catch (CipherException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
addCredentials(account, password);
return account;
}
以太币(矿币)转账
private void transfer_ETH(String _from, String password, String _to, BigInteger value, Long logId) {
Credentials credentials = loadCredentials(_from, password);
try {
//获取nonce
EthGetTransactionCount count=web3j.ethGetTransactionCount(_from, DefaultBlockParameterName.LATEST).send();
BigInteger nonce=count.getTransactionCount();
//创建交易
//BigInteger val=Convert.toWei(new BigDecimal(value), Unit.ETHER).toBigInteger();
RawTransaction rawTransaction=RawTransaction.createEtherTransaction(nonce, GAS_PRICE, GAS_LIMIT, _to, value);
//验证签名
byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
String hexValue=Numeric.toHexString(signMessage);
//发送交易
CompletableFuture<EthSendTransaction> sendAsync = web3j.ethSendRawTransaction(hexValue).sendAsync();
sendAsync.whenComplete((v,e)->{
//交易回调
new Thread(() -> {
callBackService.callbackEthSendTransaction(v, logId, e);
}).start();
});
}catch(Exception e) {
}
}
加载已有账户的凭证
keystorePath是凭证文件保存的文件夹地址,例如:/home/geth/keystore
这里读取文件之前,先去全局map(预加载)里检查是否能拿到凭证
private Credentials loadCredentials(String address,String password){
Credentials credentials=credentialsMap.get(address);
if(CommonUtil.isNull(credentials)){
//往map里加
credentials=addCredentials(address,password);
}
return credentials;
}
private Credentials addCredentials(String address,String password){
Credentials credentials=null;
try {
File file = new File(keystorePath);
File[] files = file.listFiles();
if(files!=null && files.length>0){
for (File f : files) {
String a=address.trim().substring(2);
if (f.getName().contains(a)) {
//取到这个文件,并生成credentials对象
credentials = WalletUtils.loadCredentials(password, f);
break;
}
}
}
if (!CommonUtil.isNull(credentials)){
credentialsMap.putIfAbsent(address, credentials);
}else {
throw new RuntimeException("读取凭证错误,请检查钱包文件是否存在");
}
} catch (IOException | CipherException e) {
e.printStackTrace();
}
return credentials;
}
以太坊还包括合约发布的代币-虚拟币
加载执行合约也是非常常见的
智能合约由solidity语言开发,可以编译为java文件
可以使用VScode下载solidity插件,将合约编译 .sol ---> .bin .abi .json
使用codegen-3.5.0.jar 的 SolidityFunctionWrapperGenerator.class 的 mian方法
<!-- https://mvnrepository.com/artifact/org.web3j/codegen -->
<dependency>
<groupId>org.web3j</groupId>
<artifactId>codegen</artifactId>
<version>3.5.0</version>
</dependency>
并需要附带参数
#来自https://docs.web3j.io/smart_contracts.html
$ web3j solidity generate [--javaTypes|--solidityTypes] /path/to/<smart-contract>.bin /path/to/<smart-contract>.abi -o /path/to/src/main/java -p com.your.organisation.name
private static final String USAGE = "solidity generate "
+ "[--javaTypes|--solidityTypes] "
+ "<input binary file>.bin <input abi file>.abi "
+ "-p|--package <base package name> "
+ "-o|--output <destination base directory>";
从输出文件夹拿到编译好的java合约代码
调用合约方法 加载合约需要合约地址
//GAS_PRICE可以动态获取
private BigInteger GAS_PRICE = BigInteger.valueOf(22_000_000_000L);
//GAS_LIMIT参数固定
private final BigInteger GAS_LIMIT = BigInteger.valueOf(43_000);
/**
* USDT的转账方法
*/
private void transfer_USDT(String _from, String password, String _to, BigInteger value, Long logId) {
//拿到凭证
Credentials credentials = loadCredentials(_from, password);
//使用凭证加载合约 需要知道合约的地址 这里涉及到GAS 是为交易手续费 从合约调用方账户上扣除以太币
TetherToken contract = TetherToken.load(USDTAddress, getWeb3j(), credentials, GAS_PRICE, GAS_LIMIT);
//调用合约方法
RemoteCall<TransactionReceipt> transfer = contract.transfer(_to, value);
CompletableFuture<TransactionReceipt> sendAsync = transfer.sendAsync();
//异步执行 并添加回调方法
sendAsync.whenComplete((v,e)->{
new Thread(() -> {
callBackService.callbackTransactionReceipt(v, logId, e);
}).start();
});
}
有一部分合约并不公开源码
在无法编译得到java合约代码的情况下,可以使用 abi+合约二进制代码+合约地址 调用
abi包括一个合约所有方法、参数、返回值
以下做一个简单的示例,并不支持很多参数类型
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Int;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Uint;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.tx.Contract;
import com.alibaba.fastjson.JSON;
import okkpp.function.Function;
import okkpp.function.Param;
public class WidelyContract extends Contract {
private List<Function> functions;
@SuppressWarnings("deprecation")
protected WidelyContract(String abi, String contractBinary, String contractAddress, Web3j web3j,
Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
super(contractBinary, contractAddress, web3j, credentials, gasPrice, gasLimit);
this.functions = JSON.parseArray(abi, Function.class);
}
public static WidelyContract load(String abi, String contractBinary, String contractAddress, Web3j web3j,
Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
return new WidelyContract(abi, contractBinary, contractAddress, web3j, credentials, gasPrice, gasLimit);
}
@SuppressWarnings("rawtypes")
public Type<?> excute(String functionName, String... params) throws IOException {
List<Type> inputs = null;
List<TypeReference<?>> outputs = null;
for (Function f : functions) {
if (functionName.equals(f.getName())) {
inputs = getInputs(f.getInputs(), params);
outputs = getOutputs(f.getOutputs());
}
}
return executeCallSingleValueReturn(new org.web3j.abi.datatypes.Function(functionName, inputs, outputs));
}
@SuppressWarnings("rawtypes")
private List<Type> getInputs(List<Param> inputs, String[] params) {
int size = inputs.size();
List<Type> result = new ArrayList<Type>();
for (int i = 0; i < size; i++) {
result.add(getParam(inputs.get(i).getType(), params[i]));
}
return result;
}
private List<TypeReference<?>> getOutputs(List<Param> outputs) {
int size = outputs.size();
List<TypeReference<?>> result = new ArrayList<>();
for (int i = 0; i < size; i++) {
result.add(getType(outputs.get(i).getType()));
}
return result;
}
private TypeReference<?> getType(String type) {
switch (type) {
case "address": {
return new TypeReference<Address>() {
};
}
case "string": {
return new TypeReference<Utf8String>() {
};
}
case "bool": {
return new TypeReference<Bool>() {
};
}
}
if (type.startsWith("uint")) {
return new TypeReference<Uint>() {
};
}
if (type.startsWith("int")) {
return new TypeReference<Int>() {
};
}
return null;
}
@SuppressWarnings("rawtypes")
private Type getParam(String type, String input) {
switch (type) {
case "address": {
return new Address(input);
}
case "string": {
return new Utf8String(input);
}
case "bool": {
return new Bool(input.equals("true"));
}
}
if (type.startsWith("uint")) {
return new Uint(new BigInteger(input));
}
if (type.startsWith("int")) {
return new Int(new BigInteger(input));
}
return null;
}
}
另附自定义Function类
import java.util.List;
public class Function {
private boolean constant;
private String name;
private boolean payable;
private String stateMutability;
private String type;
private List<Param> inputs;
private List<Param> outputs;
public boolean isConstant() {
return constant;
}
public void setConstant(boolean constant) {
this.constant = constant;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isPayable() {
return payable;
}
public void setPayable(boolean payable) {
this.payable = payable;
}
public String getStateMutability() {
return stateMutability;
}
public void setStateMutability(String stateMutability) {
this.stateMutability = stateMutability;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<Param> getInputs() {
return inputs;
}
public void setInputs(List<Param> inputs) {
this.inputs = inputs;
}
public List<Param> getOutputs() {
return outputs;
}
public void setOutputs(List<Param> outputs) {
this.outputs = outputs;
}
@Override
public String toString() {
return "Function [constant=" + constant + ", name=" + name + ", payable=" + payable + ", stateMutability="
+ stateMutability + ", type=" + type + ", inputs=" + inputs + ", outputs=" + outputs + "]";
}
}
与Param类
public class Param {
private String name;
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString() {
return "Param [name=" + name + ", type=" + type + "]";
}
}