web3j是一个轻量级、高度模块化、响应式、类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以太坊网络上的客户端(节点)进行集成。
汇智网最新发布的web3j教程,详细讲解如何使用web3j为Java应用或Android App增加以太坊区块链支持,课程内容即涉及以太坊中的核心概念,例如账户管理、状态与交易、智能合约开发与交互、过滤器和事件等,同时也详细说明如何使用web3j提供的开发接口与以太坊进行交互,是java工程师学习以太坊应用开发的不二选择
web3j是一个轻量级、高度模块化、响应式、类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以太坊网络上的客户端(节点)进行集成。
汇智网最新发布的web3j教程,详细讲解如何使用web3j为Java应用或Android App增加以太坊区块链支持,课程内容即涉及以太坊中的核心概念,例如账户管理、状态与交易、智能合约开发与交互、过滤器和事件等,同时也详细说明如何使用web3j提供的开发接口与以太坊进行交互,是java工程师学习以太坊应用开发的不二选择
从广义上讲,有web3j支持三种类型的以太坊交易:
1.以太币从一方交易到另一方
2.创建一个智能合约
3.与智能合约交易
为了进行这些交易,必须有以太币(以太坊区块链的代币)存在于交易发生的以太坊账户中。这是为了支付gas成本,这是为支付参与交易的以太坊客户端的交易执行成本,支付了这个成本就能将结果提交到以太坊区块链上。获得以太币的说明下文会说到。
此外,我们还可以查询智能合约的状态。
如何获得以太币Ether
要想获得以太币Ether你有两种途径可以选择:
1.自己开采挖矿
2.从别人那里获取以太币
在私有链中自己挖矿,或者公共测试链(testnet)是非常简单直接的。但是,在主要的公有链(mainnet)中,它需要很多很明显的专用GPU时间,除非你已经拥有多个专用GPU的矿机,否则基本上不太可行。如果你希望使用私有链,则在这个官方文档中有一些指导。
要购买以太币Ether,你需要通过交易所。由于不同的地区有不同的交易所,你还需要研究自己去哪儿合适。官方文档中包含多个交易所,是一个很好的参考。
以太坊测试链(testnets)
针对Ethereum以太坊有许多专用测试网络或者叫测试链,他们由各种客户端支持。
1.Rinkeby:只支持geth客户端。
2.Kovan:只支持Parity客户端。
3.Ropsten:支持geth和Parity客户端。
对于开发,建议你使用Rinkeby或KoVan测试链。这是因为他们使用的工作量证明POA共识机制,确保交易和块能够一致并及时的创建。Ropsten测试链,虽然最接近公有链(Mainnet),但是因为它使用的工作量证明是POW共识机制,过去已受到攻击,对以太坊开发人员来说往往有更多的问题。
你可以通过Rinkeby测试链的Rinkeby Crypto Fauce请求以太坊币,具体怎么做可以看这里https://www.rinkeby.io/。
有关如何请求Kovan测试链的细节可以在这里找到。
如果你需要在Ropsten上的得到一些以太币,将你的钱包地址的消息发布到web3j gitter channel,然后会发送一些给你。
在testnet测试链或者私有链上挖掘
在ethereum以太坊测试链testnet中,挖掘难度低于公有链mainnet。这意味着你可以用普通的CPU,比如你的笔记本电脑来挖掘新的以太币。你需要做的是运行一个以太坊客户端,例如geth或Parity,开始做一些储备。进一步的资料可在他们的官方网站上获得。
一旦你开采了一些以太币,你就可以开始使用以太坊区块链了。
然而,如上所述,使用Kovan或者Rinkeby测试网络更简单些。
gas
当在Ethereum以太坊发生交易时,必须为执行该交易的客户端支付交易成本,将该交易的输出提交到以太坊区块链Ethereum blockchain。
此成本是通过gas来测量的,其中gas是用于在以太坊虚拟机中执行交易指令的数量。请参阅官方文档以获取更多信息。
当你使用以太坊客户端时,这意味着,有两个参数用来指示你希望花费多少以太来完成传输:
gas price :气体价格,这是每单位gas中以太的消耗量。Web3j使用的默认价格为22000000000 wei(22×10-8 Ether)。这是在交易管理中定义的。
gas limit:气体最大量,这是你愿意在交易执行上花费的gas的最大总量。单个交易在一个以太坊区块中有多大的上限,通常将该值限制为小于6700000。当前的gas限制在这里查https://ethstats.net/。
这两个参数共同决定了你愿意花费在交易成本上的最大量的以太币Ether。也就是说,你花费的gas不会超过gas price * gas limit。gas价格也会影响交易发生的速度,这取决于其他交易是否能为矿工提供更有利的gas价格。
你可能需要调整这些参数以确保交易能及时进行。
交易机制
当你用一些以太币Ether创建了一个有效的帐户时,你可以使用两种机制来与以太坊进行交易。
通过以太坊ethereum客户端进行认证签名交易
离线交易签名认证
这两种机制都是Web3j所支持的。
通过以太坊ethereum客户端进行认证签名交易
为了通过以太坊客户端进行交易,首先需要确保你正在使用的客户端知道你的钱包地址。最好是运行自己的以太坊客户端,比如geth/Parity,以便可以更方便的做到这一点。一旦你有一个客户端运行,你可以创建一个以太坊钱包,通过:
geth Wiki包含了geth支持的良好运行的不同机制,例如导入私有密钥文件,并通过控制台创建新的以太坊帐户。
或者,你可以通过客户端使用JSON-RPC管理命令,例如用personal_newAccount为geth/Parity创建新以太坊账户。
通过创建你的钱包文件,你可以通过web3j打开帐户,首先创建支持geth/Parity管理命令的web3j实例:
Admin web3j = Admin.build(new HttpService());
然后,你可以解锁帐户,并如果是成功的,就可以发送一个交易:
PersonalUnlockAccount personalUnlockAccount = web3j.personalUnlockAccount("0x000...", "a password").send();
if (personalUnlockAccount.accountUnlocked()) {
// send a transaction
}
Transaction transaction = Transaction.createContractTransaction(
,
,
BigInteger.valueOf(), // we use default gas limit
"0x..."
);
org.web3j.protocol.core.methods.response.EthSendTransaction
transactionResponse = parity.ethSendTransaction(ethSendTransaction)
.send();
String transactionHash = transactionResponse.getTransactionHash();
// poll for transaction response via org.web3j.protocol.Web3j.ethGetTransactionReceipt()
其中nonce值获得方式,下文会提到。 有关此交易工作流的详细信息,请参阅DeployContractIT和Scenario。
web3j支持的各种管理命令的进一步细节在Management APIs中。
离线交易签名认证Offline transaction signing
如果你不想管理自己的以太坊客户端,或者不想向以太坊客户端提供诸如密码之类的钱包详细信息,那么就通过离线交易认证签名。
离线交易签名认证允许你在web3j中使用你的以太坊钱包签署交易,允许你完全控制你的私有凭据。然后,离线创建的交易可以被发送到网络上的任何以太坊客户端,只要它是一个有效的交易,它会将交易传播到其他节点。
如果需要,还可以执行进程外交易签名认证。这可以通过重写ECKeyPair的sign方法来实现。
创建和使用钱包文件Ethereum wallet file
为了离线脱机交易,你需要有你的钱包文件或与私密钱包/账户相关的公共和私人密钥。
web3j能够为你生成一个新的安全的以太坊钱包文件Ethereum wallet file,或者与也可以通过私钥来和现有的钱包文件一起工作。
创建新的钱包文件:
String fileName = WalletUtils.generateNewWalletFile(
"your password",
new File("/path/to/destination"));
加载凭据从钱包文件:
Credentials credentials = WalletUtils.loadCredentials(
"your password",
"/path/to/walletfile");
然后这些凭据会被用来签署交易,请参阅Web3安全存储定义钱包文件规范Web3 Secret Storage Definition
签署以太坊交易
要使脱机签名交易得到签署,需要设定一个RawTransaction类型。RawTransaction类似于前面提到的Transaction类型,但是它不需要通过具体的账号地址来请求,因为可以从签名中推断出来。
为了创建和签署原生交易,交易的顺序如下:
1.确定交易发起者帐户的下一个可用随机数nonce
2.创建RawTransaction对象
3.使用递归长度前缀编码(RLP即Recursive Length Prefix)对RawTransaction对象进行编码
4.签署RawTransaction对象
5.将RawTransaction对象发送到节点进行处理
nonce是一个不断增长的数值,用来唯一地标识交易。一个nonce只能使用一次,直到交易被挖掘完成,可以以相同的随机数发送交易的多个版本,但是一旦其中一个被挖掘完成,其他后续提交的都将被拒绝。
一旦获得下一个可用的nonce,该值就可以用来创建transaction对象:
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
nonce, , , , );
然后可以对交易进行签名和编码:
byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, );
String hexValue = Numeric.toHexString(signedMessage);
其中凭据是根据创建和使用钱包文件加载的。
EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).sendAsync().get();
String transactionHash = ethSendTransaction.getTransactionHash();
// poll for transaction response via org.web3j.protocol.Web3j.ethGetTransactionReceipt()
交易随机数nonce
nonce是一个不断增长的数值,用来唯一地标识交易。一个nonce只能使用一次,直到交易被挖掘完成,可以以相同的随机数发送交易的多个版本,但是一旦其中一个被挖掘完成,其他后续提交的都将被拒绝。
可以通过eth_getTransactionCount方法获得下一个可用的nonce:
EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
address, DefaultBlockParameterName.LATEST).sendAsync().get();
BigInteger nonce = ethGetTransactionCount.getTransactionCount();
然后可以使用nonce创建你的交易对象:
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
nonce, , , , );
交易类型
web3j中的不同类型的交易都使用Transaction和RawTransaction对象。关键的区别是交易对象必须始终有一个地址,以便处理eth_sendTransaction请求的以太坊客户端知道要使用哪个钱包来代表消息发送者并发送该交易。如上所述,对于离线签名认证签署的原始交易而言,这不是必须的。
接下来的部分概述了不同交易类型所需的关键交易属性。下列属性对所有人都是不变:
Gas price 天然气气体价格
Gas limit 天然气气体限制
Nonce 随机数
from 发送地址
Transaction和RawTransaction对象在所有后续示例中都可互换使用。
以太币从一方交易到另一方
在双方之间发送以太币Ether需要交易对象的最少量的信息:
to :目的地钱包地址
value:价值,希望发送到目的地的以太币数量
BigInteger value = Convert.toWei("1.0", Convert.Unit.ETHER).toBigInteger();
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
, , , , value);
// send...
但是,建议你使用TransferClass来发送以太币Ether,它负责对nonce管理和通过不断的轮询为你提供响应:
Web3j web3 = Web3j.build(new HttpService()); // defaults to http://localhost:8545/
Credentials credentials = WalletUtils.loadCredentials("password", "/path/to/walletfile");
TransactionReceipt transactionReceipt = Transfer.sendFunds(
web3, credentials, "0x
|",BigDecimal.valueOf(1.0), Convert.Unit.ETHER).send();
使用智能合约打包器smart contract wrappers
当使用下面列出的智能合约打包器时,将不得不手动执行从Solidity到本机Java类型的所有转换。使用Solidity smart contract wrappers是非常有效的,它负责所有的代码生成和转换。
创建一个智能合约
要部署新的智能合约,需要提供以下属性:
value :在智能合约中希望存放的以太坊Ether量(如果没有提供默认为零)
data :十六进制格式化、编译的智能合约创建代码
// using a raw transaction
RawTransaction rawTransaction = RawTransaction.createContractTransaction(
,
,
,
,
"0x ");
// send...
// get contract address
EthGetTransactionReceipt transactionReceipt =
web3j.ethGetTransactionReceipt(transactionHash).send();
if (transactionReceipt.getTransactionReceipt.isPresent()) {
String contractAddress = transactionReceipt.get().getContractAddress();
} else {
// try again
}
如果智能合约包含构造函数,则必须对关联的构造函数字段值进行编码,并将其附加到编译的智能合约代码中compiled smart contract code:
String encodedConstructor =
FunctionEncoder.encodeConstructor(Arrays.asList(new Type(value), ...));
// using a regular transaction
Transaction transaction = Transaction.createContractTransaction(
,
,
,
,
,
"0x " + encodedConstructor);
// send...
与智能合约交易
要与现有的智能合约进行交易,需要提供以下属性:
to:智能合同地址
value:在智能合约中你希望存放的以太币Ether量(如果智能合约接受以太币Ether的话)
data: 已编码的函数选择器和自变量参数
web3j负责函数编码,有关实现的进一步细节,请参阅应用程序二进制接口部分Application Binary Interface。
Function function = new Function<>(
"functionName", // function we're calling
Arrays.asList(new Type(value), ...), // Parameters to pass as Solidity Types
Arrays.asList(new TypeReference() {}, ...));
String encodedFunction = FunctionEncoder.encode(function)
Transaction transaction = Transaction.createFunctionCallTransaction(
, , , contractAddress, , encodedFunction);
org.web3j.protocol.core.methods.response.EthSendTransaction transactionResponse =
web3j.ethSendTransaction(transaction).sendAsync().get();
String transactionHash = transactionResponse.getTransactionHash();
// wait for response using EthGetTransactionReceipt...
无论消息签名的返回类型如何,都不可能从事务性函数调用返回值。但是,使用过滤器捕获函数返回的值是可能的。详情请参阅过滤器和事件部分。
查询智能合约状态
这种功能是由eth_call通过JSON-RPC调用来实现的。
eth_call允许你调用智能合约上的方法来查询某个值。此函数没有关联交易成本,这是因为它不改变任何智能合约方法的状态,它只返回它们的值:
Function function = new Function<>(
"functionName",
Arrays.asList(new Type(value)), // Solidity Types in smart contract functions
Arrays.asList(new TypeReference() {}, ...));
String encodedFunction = FunctionEncoder.encode(function)
org.web3j.protocol.core.methods.response.EthCall response = web3j.ethCall(
Transaction.createEthCallTransaction(, contractAddress, encodedFunction),
DefaultBlockParameterName.LATEST)
.sendAsync().get();
List someTypes = FunctionReturnDecoder.decode(
response.getValue(), function.getOutputParameters());
注意:如果一个无效的函数调用被执行,或者得到一个空null返回结果时,返回值将是一个Collections.emptyList实例。
web3j教程,主要是针对java和android程序员进行区块链以太坊开发的web3j开发详解。
以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。
以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
汇智网原创翻译,转载请标明出处。原文访问其官方博客
windows
java 17.0.1
maven 3.8.3
node 14.17.0
安装ganache-cli(用于搭建以太坊私链),并启动ganache-cli
npm install -g ganache-cli
ganache-cli
启动后可以看到如下信息
新建一个maven项目,加上下面两个依赖
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>4.8.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.6</version>
</dependency>
代码如下(暂时没有智能合约,只是简单的获取区块链信息和普通交易):
package com.example;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.*;
import org.web3j.protocol.core.methods.response.EthBlock.Block;
import org.web3j.protocol.core.methods.response.EthBlock.TransactionResult;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.Transfer;
import org.web3j.utils.Convert;
import org.web3j.utils.Convert.Unit;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
try {
//连接以太坊客户端节点,可以是ganache,geth等,注意地址端口和RPC协议(ws ,http)
Web3j web3j = Web3j.build(new HttpService("http://127.0.0.1:8545"));
//获取客户端版本号,通常用来判断是否连接上
Web3ClientVersion web3clientversion = web3j.web3ClientVersion().send();
String clientVersion = web3clientversion.getWeb3ClientVersion();
System.out.println("web3clientVersion : " + clientVersion);
//获取节点上所有的账户,在ganache中,每个账户有100ETH,允许你用上面用户交易
//注意绝大多数以太坊客户端节点不会允许你用它上面的账户交易,你要自己创建钱包(见后面的Cpp类main函数)
List<String> addressList = web3j.ethAccounts().send().getAccounts();
System.out.println("addressList : "+addressList);
//交易前,获取前两个账户余额
BigInteger balance0 = web3j.ethGetBalance(addressList.get(0), DefaultBlockParameterName.LATEST).send().getBalance();
BigInteger balance1 = web3j.ethGetBalance(addressList.get(1), DefaultBlockParameterName.LATEST).send().getBalance();
System.out.println("before Transaction:");
System.out.println("balance0 : " + addressList.get(0) + " : " + balance0);
System.out.println("balance1 : " + addressList.get(1) + " : " + balance1);
//生成交易,参数包括谁发送,发给谁,发多少,给矿工的小费等等,由于ganache允许我们操作它上面的账户,所以不用签名。
BigInteger mynounce = web3j.ethGetTransactionCount(addressList.get(0), DefaultBlockParameterName.LATEST).send().getTransactionCount();
BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();
BigInteger gasLimit = BigInteger.valueOf(21000);
BigInteger value = Convert.toWei(BigDecimal.valueOf(0.5), Unit.ETHER).toBigInteger() ;
Transaction mytesttransaction= Transaction.createEtherTransaction(addressList.get(0),mynounce, gasPrice, gasLimit, addressList.get(1),value);
System.out.println("mytesttransaction : ");
printTransaction(mytesttransaction);
//发送交易,获取交易哈希
String txhash = web3j.ethSendTransaction(mytesttransaction).send().getTransactionHash();
System.out.println("txhash : " + txhash);
//交易对应的收据,这个对于智能合约交易非常有用,对普通交易没多大用
TransactionReceipt transactionReceipt = web3j.ethGetTransactionReceipt(txhash).send().getTransactionReceipt().get();
System.out.println("transactionReceipt : " + transactionReceipt);
//交易后的余额
balance0 = web3j.ethGetBalance(addressList.get(0), DefaultBlockParameterName.LATEST).send().getBalance();
balance1 = web3j.ethGetBalance(addressList.get(1), DefaultBlockParameterName.LATEST).send().getBalance();
System.out.println("after Transaction:");
System.out.println("balance0 : " + addressList.get(0) + " : " + balance0);
System.out.println("balance1 : " + addressList.get(1) + " : " + balance1);
//获取当前最新区块,并获取它上面的所有交易
Block latestBlock = web3j.ethGetBlockByNumber(DefaultBlockParameterName.LATEST, false).send().getBlock();
System.out.println("latestBlock: " + latestBlock.getHash());
//获取区块上的所有交易
List<TransactionResult> txlist = latestBlock.getTransactions();
System.out.println("get TransactionBy Block :" );
for (TransactionResult transactionResult : txlist) {
System.out.println(transactionResult.get());
}
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
}
public static void printTransaction(Transaction x) {
//打印交易信息
System.out.println("From:"+x.getFrom());
System.out.println("GasLimit:"+x.getGas());
System.out.println("Nonce:"+x.getNonce());
System.out.println("GasPrice:"+x.getGasPrice());
System.out.println("To:"+x.getTo());
System.out.println("Value:"+x.getValue());
}
}
class Cpp {
public static void main(String[] args) {
try {
Web3j web3j = Web3j.build(new HttpService("http://127.0.0.1:8545"));
//通常情况下,以太坊节点是不会让其他人用它上面的账户,我们要自己创建钱包,用自己的私钥对交易签名,
//下面的私钥是ganache上第一个账户的私钥,虽然交易发起者和上面的交易一样,但两者本质是不同的。
Credentials mysigner = Credentials.create("0xfd468e650d82240e739c18ea1aaa0a7316febec3e7bb056ff1437bd3d0ede99b");
List<String> addressList = web3j.ethAccounts().send().getAccounts();
String txhash = Transfer.sendFunds(web3j, mysigner, addressList.get(1),BigDecimal.valueOf(1.0), Unit.ETHER).send().getTransactionHash();
System.out.println("txhash:"+txhash);
TransactionReceipt receipt = web3j.ethGetTransactionReceipt(txhash).send().getTransactionReceipt().get();
System.out.println(receipt);
} catch (Exception e) {
e.printStackTrace();
}
}
}
App中main执行结果:
Cpp中main执行结果:
首先安装solc(用于编译智能合约)和web3j命令行工具(用于打包智能合约)
npm install -g solc
web3j安装地址: Releases · web3j/web3j · GitHub,选择对应操作系统
首先准备一个智能合约 Owner.sol,建议先在remix上测试一下Remix - Ethereum IDE
//SPDX-License-Identifier:UNLICENSED
pragma solidity >=0.7.0 <0.9.0;
contract Qwner {
address private owner;
event OwnerSet(address oldAddress,address newAddress);
modifier isOwner(){
require (msg.sender==owner,"Caller is not owner!");
_;
}
constructor (){
owner = msg.sender;
emit OwnerSet( address(0) , owner);
}
function changeOwner (address newOwner) public isOwner {
emit OwnerSet(owner, newOwner);
owner = newOwner;
}
function getOwner()public view returns (address) {
return owner;
}
}
先编译 solcjs Owner.sol --bin --abi --optimize -o .\ 然后你可以看到当前文件夹下生成了两个文件,.abi和.bin,这名字有点反人类,最好改成Owner.abi和Owner.bin
然后打包成把合约打包成Java文件使用如下命令,其中-p是指定包名
web3j generate solidity -a src\main\Owner.abi -b src\main\Owner.bin -o src\main\java\ -p com.example
执行完上述命令后可以看到src\main\java\com\example下多了一个Owner.java文件
接着就是代码,包括部署智能合约,调用智能合约的函数:
package com.example;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.util.List;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.gas.ContractGasProvider;
import org.web3j.tx.gas.DefaultGasProvider;
class Cpp{
public static void main(String[] args) {
try {
Web3j web3j = Web3j.build(new HttpService("http://127.0.0.1:8545"));
//创建钱包
Credentials defaulCredential = Credentials.create(App.privateKey1);
BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();
//这个类的作用是你能够根据你不同函数名称灵活的选择不同的GasPrice和GasLimit
ContractGasProvider myGasProvider = new DefaultGasProvider(){
@Override
public BigInteger getGasPrice(String contractFunc ) {
return gasPrice;
}
@Override
public BigInteger getGasLimit(String contractFunc ){
return BigInteger.valueOf(6721975);
}
};
//部署合约,并打印合约地址
Owner myContract = Owner.deploy(web3j, defaulCredential, myGasProvider).send();
System.out.println("deploy contract: "+myContract.isValid());
System.out.println("contract adddress: "+myContract.getContractAddress());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class App
{
static Web3j web3j = null;
//ganache上的accounts[0]和accounts[1]的私钥
static String privateKey1 = "0x64685c9589ef1e937e8009eba589059d4b7b10bb44a6efc6eeb436c7f47ab85c";
static String privateKey2 = "0xae7d780682ee82c5301152bec4ddb51ada56944135d211130a582f33c52d7c1d";
//硬编码,填入合约部署后输出的合约地址
static String contractAddress = "0x30a856cd0aaf589b99152331ae4bc1193ed32570";
public static void main(String[] args )
{
try {
web3j = Web3j.build(new HttpService("http://127.0.0.1:8545"));
String clientVersion = web3j.web3ClientVersion().send().getWeb3ClientVersion();
System.out.println("clientVersion : "+clientVersion);
List<String> accounts = web3j.ethAccounts().send().getAccounts();
System.out.println("accounts"+accounts);
//连个私钥accounts[0]和accounts[1]
Credentials defaulCredential = Credentials.create(privateKey1);
Credentials secondCredentials = Credentials.create(privateKey2);
BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();
ContractGasProvider myGasProvider = new DefaultGasProvider(){
@Override
public BigInteger getGasPrice(String contractFunc ) {
return gasPrice;
}
@Override
public BigInteger getGasLimit(String contractFunc ){
return BigInteger.valueOf(6721975);
}
};
BufferedReader br = new BufferedReader( new InputStreamReader(System.in));
String command;
//根据地址获取合约实例,注意这两个合约实例不同之处在于他们的用户不同,第一个合约对应accounts[0],第二给合约对应accounts[1]
Owner myContract = Owner.load(contractAddress, web3j,defaulCredential , myGasProvider);
Owner secondContract = Owner.load(contractAddress, web3j,secondCredentials , myGasProvider);
//判断合约是否合法,这一步也很重要
if(myContract.isValid()==false||secondContract.isValid()==false) {
throw new Exception("load contract error");
}
System.out.println("load contract: "+myContract.isValid()+" "+secondContract.isValid());
System.out.println("contract adddress: "+myContract.getContractAddress()+" "+secondContract.getContractAddress());
boolean exit = false;
TransactionReceipt receipt;
while (exit == false) {
System.out.println("please input command :");
command = br.readLine();
switch (command){
case "set":{
//accounts[0]把Owner设成accounts[1],accounts[1]再设成accounts[0],如此循环
if( myContract.getOwner().send().equals( accounts.get(0) ) ){
receipt = myContract.changeOwner(accounts.get(1)).send();
}
else {
receipt = secondContract.changeOwner(accounts.get(0)).send();
}
System.out.println(receipt);
System.out.println("now owner is ");
System.out.println(myContract.getOwner().send());
break;
}
case "get":{
System.out.println("now owner is ");
System.out.println(myContract.getOwner().send());
break;
}
case "exit": {
System.out.println("you type exit");
exit = true;
break;
}
default :{
System.out.println("wrong command input again");
break;
}
}
}
} catch (Exception e) {
System.err.println(e);
}
}
}
部署合约(Cpp中main函数输出)
加载合约并调用合约函数 (App中main函数输出)
还能监听区块链事件,主要有三种:监听区块,监听交易,监听合约事件
package com.example;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigInteger;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.gas.ContractGasProvider;
import org.web3j.tx.gas.DefaultGasProvider;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
public class App
{
static Web3j web3j = Web3j.build(new HttpService("http://127.0.0.1:8545"));
static String privateKey = "0x36308cc80ca2e632b0fb90d8acbe316d4c23f782f03131d4406a0026ed5de9e7";
static String contractAddress = "0x30a856cd0aaf589b99152331ae4bc1193ed32570";
static DefaultBlockParameterName earliest = DefaultBlockParameterName.EARLIEST;
static DefaultBlockParameterName latest = DefaultBlockParameterName.LATEST;
public static void main( String[] args )
{
try {
String clientVersion = web3j.web3ClientVersion().send().getWeb3ClientVersion();
System.out.println(clientVersion);
Credentials defaulCredential = Credentials.create(privateKey);
BigInteger gasLimit = BigInteger.valueOf(6721975);
BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();
ContractGasProvider simplProvider = new DefaultGasProvider(){
@Override
public BigInteger getGasPrice(String contractFunc ) {
return gasPrice;
}
@Override
public BigInteger getGasLimit(String contractFunc ){
return gasLimit;
}
};
Owner mycontract = Owner.load(contractAddress, web3j, defaulCredential, simplProvider);
if(mycontract.isValid()==false){
throw new Exception("load error");
}
System.out.println("load contract: "+mycontract.isValid());
//使用CompositeDisposable
CompositeDisposable disposableSet = new CompositeDisposable();
//监听区块
Disposable blockSubscription =web3j.blockFlowable(false).subscribe( block -> {
System.out.println("new block: "+block.getBlock().getHash());
} );
//监听交易
Disposable txsubcription = web3j.transactionFlowable().subscribe( tx -> {
System.out.println("new tx: from "+tx.getFrom()+ "; to "+tx.getTo());
} );
//监听合约的OwnerSet事件
Disposable ownersetSubscription = mycontract.ownerSetEventFlowable(latest, latest).subscribe(res -> {
System.out.println(res.log);
System.out.println("owner chage: old "+res.oldAddress+" new "+res.newAddress);
});
disposableSet.add(blockSubscription);
disposableSet.add(txsubcription);
disposableSet.add(ownersetSubscription);
System.out.println(disposableSet);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String command =null;
while(true){
System.out.println("input command: ");
command = br.readLine();
if(command.equals("stop")){
disposableSet.dispose();
System.out.println(disposableSet);
break;
} else {
continue;
}
}
} catch (Exception e) {
System.err.println(e);
}
}
}
控制台输出: