web3jは、Ethereumのクライアント(ノード)とJSON-RPCで通信できるJavaライブラリです。
この記事では、web3jの基本的な使い方を紹介します。
<トピック>
- Ethereumクライアントへの接続
- EthereumクライアントとJSON-RPCで通信
- web3_clientVersion
- eth_getBlockByNumber
- eth_sendTransaction
- 便利な機能の紹介
- Walletファイルを使った処理
- コントラクトのラッパークラス
- イベントをリアクティブに処理
Ethereumクライアントへの接続
まずはEthereumクライアントを起動し、JSON-RPCによる通信が可能な状態にしましょう。
例えばGeth
を起動し、HTTPによるJSON-RPC通信が可能な状態にします。
準備ができたら、web3jを使ってEthereumクライアントに接続します。
// HTTPによる接続
Web3j web3j = Web3j.build(new HttpService("http://<ip address>:<port>"));
// IPCによる接続(Unix)
Web3j web3 = Web3j.build(new UnixIpcService("/path/to/socketfile"));
// IPCによる接続(Windows)
Web3j web3 = Web3j.build(new WindowsIpcService("/path/to/namedpipefile"));
EthereumクライアントとJSON-RPCで通信
Ethereumクライアントへの接続が成功したら、次はJSON-RPCで通信してみましょう。
web3jによるJSON-RPC通信はとても簡単です。
web3_clientVersion
例えばJSON-RPCのweb3_clientVersion
メソッドを呼び出す場合は、下記のように記述します。
(web3_clientVersion
は、Ethereumクライアントのバージョンを確認するメソッドです)
// Ethereumクライアントと接続
Web3j web3j = Web3j.build(new HttpService("http://localhost:8545"));
// JSON-RPCのリクエストを送信し、レスポンスを受信する
Web3ClientVersion response = web3j.web3ClientVersion().send();
// 結果を出力
System.out.println(response.getResult());
Geth/v1.8.17-stable-8bbe7207/windows-amd64/go1.11.1
このように、web3jでは、JSON-RPCの各メソッドをリクエストするための専用のJavaメソッドが用意されています。
eth_getBlockByNumber
次に、メソッド引数を指定する必要のある例を紹介します。
ここでは、JSON-RPCのeth_getBlockByNumber
をメソッドを使って、最新("latest")のブロック情報を取得してみましょう。
// Ethereumクライアントと接続
Web3j web3j = Web3j.build(new HttpService("http://localhost:8545"));
// JSON-RPCのリクエストを送信し、レスポンスを受信する
EthBlock response = web3j.ethGetBlockByNumber(
DefaultBlockParameterName.LATEST, // block number。ここでは最新のブロックを表すタグである"latest"を指定する
false // trueの場合、トランザクションの詳細を取得する。falseの場合、トランザクションハッシュのみ取得する
).send();
// 結果を出力
Block block = response.getResult();
System.out.println("hash:" + block.getHash());
System.out.println("number:" + block.getNumber());
hash:0x7abc74f4b77054a378cd8969b77ca207ed74ff925bd751314186180d37818d33
number:7311
このように、メソッド引数もJSON-RPCの仕様に沿って指定できますので、とても分かりやすいと思います。
eth_sendTransaction
次に、トランザクションの発行を伴う例を紹介します。
ここでは、JSON-RPCのeth_sendTransaction
を使って、Etherを送金しましょう。
ただしその前に、アカウントをアンロックする必要があります。
EthereumクライアントがGeth
の場合、JSON-RPCでpersonal_unlockAccount
メソッドが提供されていますので、これを利用してアカウントをアンロックした後に、Etherを送金しましょう。
// Ethereumクライアントと接続
// AdminはWeb3jのサブクラスで、"personal_unlockAccount"のリクエスト処理が実装されています。
Admin web3j = Admin.build(new HttpService("http://localhost:8545"));
// "personal_unlockAccount"のリクエストを送信し、レスポンスを受信する
PersonalUnlockAccount unlockAccountResponse = web3j.personalUnlockAccount(
"0x766a1c4737970feddde6f2b8659fca05bd0339ab", // アドレス
"pass1" // パスワード
).send();
// アンロックが成功していたら、Etherを送金する
if (unlockAccountResponse.getResult()) {
// "eth_sendTransaction"の引数に渡すオブジェクトを作成
Transaction transaction = Transaction.createEtherTransaction(
"0x766a1c4737970feddde6f2b8659fca05bd0339ab", // from
null, // nonce。今回は指定しないため、null
null, // gasPrice。今回は指定しないため、null
null, // gasLimit。今回は指定しないため、null
"0x0cbc0fe59d39e58d7369ca436fe5c6b4b71341d5", // to
BigInteger.valueOf(100) // value
);
// "eth_sendTransaction"のリクエストを送信し、レスポンスを受信する
EthSendTransaction sendTransactionResponse = web3j.ethSendTransaction(transaction).send();
// 結果を出力
System.out.println(sendTransactionResponse.getResult());
}
0x3557db13b5ac56f1e3e23ab9eeccd11b2c4daf73e416496d53c5bba377202a61
便利な機能の紹介
web3jは、単にJSON-RPCのメソッドを簡単に呼び出すだけではない、便利な機能を提供しています。
ここでは、下記3つの機能について紹介します。
- Walletファイルを使った処理
- コントラクトのラッパークラス
- イベントをリアクティブに処理
Walletファイルを使った処理
Walletファイルってなんだ? と思うかもしれません。
これはGeth
では「keyファイル」と呼ばれているものです。keyファイルはgeth account new
を実行した際に作成されます。
通常Geth
では「<datadir>/keystore」にkeyファイルが配置されています。
この「keyファイル」のことを、web3jでは「Walletファイル」と呼んでいます。
web3jでは、Walletファイルを利用することで、アカウントのアンロックに関わる処理をシンプルにしています。
ここでは、Walletファイルを利用してEtherを送金してみましょう。
まずは、Walletファイルを、web3jのプログラムを実行する環境からアクセスできる場所に配置(コピー)しましょう。
準備ができたら、Walletファイルを利用してEtherを送金します。
// Ethereumクライアントと接続
Web3j web3j = Web3j.build(new HttpService("http://localhost:8545"));
// Walletファイルを読み込み
Credentials credentials = WalletUtils.loadCredentials(
"pass1", // アカウントのパスワード
"/pass/to/walletfile"
);
// Etherを送金
TransactionReceipt receipt = Transfer.sendFunds(
web3j,
credentials,
"0xdc87fc3ac1ec09ed43623dd9da4c6f3d792a88f5", // to
new BigDecimal("1000"), // value
Unit.ETHER // unit
).send();
一度Walletファイルをロードすれば、何度でも利用することができます。
複数のトランザクションを発行したい場合に、何度もpersonal_unlockAccount
をリクエストする必要がないため、処理がシンプルになります。
コントラクトのラッパークラス
web3jでは、コントラクトのラッパークラスを生成するツール(コマンドラインツール)を提供しています。
ラッパークラスを利用すれば、コントラクトのデプロイやメソッド呼び出しがとてもシンプルになります。
(もちろん、ラッパークラスを利用せずに、JSON-RPCのeth_sendTransaction
やeth_call
をリクエストしてコントラクトのデプロイやメソッド呼び出しを行うことも可能です。)
ここでは、サンプルとして、Solidityで書いた下記のソースのラッパークラスを生成します。
pragma solidity ^0.5.0;
contract Token {
uint256 public totalSupply;
mapping (address => uint256) public balanceOf;
constructor (uint256 _initialSupply) public {
totalSupply = _initialSupply;
balanceOf[msg.sender] = _initialSupply;
}
function transfer(address _to, uint256 _value) public {
require(balanceOf[msg.sender] >= _value);
require(balanceOf[_to] + _value >= balanceOf[_to]);
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
}
まずはオンラインのコンパイラーなどを利用して、ソースをコンパイルしましょう。
ソースをコンパイルすると、ABIとバイトコードを取得できます。
取得したABIとバイトコードをファイルに保存してください。
サンプルでは、以下のようにABIとバイトコードをファイルに保存しました。
[{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_initialSupply","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]
0x608060405234801561001057600080fd5b506040516020806103a28339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550506103068061009c6000396000f3fe608060405260043610610051576000357c01000000000000000000000000000000000000000000000000000000009004806318160ddd1461005657806370a0823114610081578063a9059cbb146100e6575b600080fd5b34801561006257600080fd5b5061006b610141565b6040518082815260200191505060405180910390f35b34801561008d57600080fd5b506100d0600480360360208110156100a457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610147565b6040518082815260200191505060405180910390f35b3480156100f257600080fd5b5061013f6004803603604081101561010957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061015f565b005b60005481565b60016020528060005260406000206000915090505481565b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156101ad57600080fd5b600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205481600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054011015151561023c57600080fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550505056fea165627a7a72305820f97c3c938c56e1b594db9c9b681f3a08bb4ef81ef0fa5888d5982034167a44ba0029
次に、コマンドラインツールを入手しましょう。
コマンドラインツールはGitHubのリリースページからダウンロードできます。
ダウンロードした圧縮ファイルを解凍し、binディレクトリ配下にある実行ファイルを以下の通り実行します。
web3j solidity generate -a=/path/to/Token.abi -b=/path/to/Token.bin -o=/path/to/src/main/java -p=package.name
コマンドが無事に成功すると、-oと-pで指定したディレクトリに「Token.java」という名前でラッパークラスが生成されます。
準備が完了しましたので、ラッパークラスを利用して、コントラクトをデプロイとメソッド呼び出しを行ってみましょう。
// Ethereumクライアントと接続
Web3j web3j = Web3j.build(new HttpService("http://localhost:8545"));
// Walletファイルを読み込み
Credentials credentials = WalletUtils.loadCredentials(
"pass1", // アカウントのパスワード
"/pass/to/walletfile"
);
// コントラクトをデプロイ
Token token = Token.deploy(
web3j,
credentials,
BigInteger.valueOf(1000000000L), // gasPrice
BigInteger.valueOf(4700000L), // gasLimit
BigInteger.valueOf(100000000000L) // initialSupply(コンストラクタ引数)
).send();
// コントラクトのアドレスを出力
System.out.println("contract address: " + token.getContractAddress());
// メソッド呼び出し(トランザクションなし)
System.out.println("totalSupply: " + token.totalSupply().send());
System.out.println("balance of user1: " + token.balanceOf("0x766a1c4737970feddde6f2b8659fca05bd0339ab").send());
// メソッド呼び出し(トランザクションあり)
System.out.println("Sending ether to user2...");
TransactionReceipt tran = token.transfer("0xdc87fc3ac1ec09ed43623dd9da4c6f3d792a88f5", BigInteger.valueOf(1000L)).send();
// 結果出力
System.out.println("transaction hash: " + tran.getTransactionHash());
System.out.println("balance of user1: " + token.balanceOf("0x766a1c4737970feddde6f2b8659fca05bd0339ab").send());
System.out.println("balance of user2: " + token.balanceOf("0xdc87fc3ac1ec09ed43623dd9da4c6f3d792a88f5").send());
contract address: 0x7b1530efbcbc4e9588fea3e505a95a3aeb84f3ed
totalSupply: 100000000000
balance of user1: 100000000000
Sending ether to user2...
transaction hash: 0x47cea479cd5aa3b4fc2fda5a0fcf6f5edc83ad2692a10355338b43aad9de41ef
balance of user1: 99999999000
balance of user2: 1000
コントラクトのデプロイやメソッド呼び出しを、とてもシンプルに記述できているのが分かると思います。
イベントをリアクティブに処理
最後にイベント監視の機能を紹介します。
web3jでは、Ethereumのイベントをリアクティブに処理する機能を提供しています。
JSON-RPCを使ったイベント監視は、通常であれば、まずeth_newBlockFilter
やeth_newFilter
等を使ってフィルターを登録し、次にeth_getFilterChanges
を使ってイベントをポーリングする、という流れになります。
これはとても面倒ですよね。
web3jが提供する機能を利用すれば、イベント監視は以下のように記述できます。
// Ethereumクライアントと接続
Web3j web3j = Web3j.build(new HttpService("http://localhost:8545"));
// フィルターを登録(イベント監視を開始)
Disposable disposable = web3j.blockFlowable(false).subscribe(ethBlock -> {
// 新しいブロックが発生した際に、以下の処理を行う
Block block = ethBlock.getBlock();
System.out.println(block.getNumber());
});
// フィルターを削除(イベント監視を終了)
disposable.dispose();
とてもシンプルに書けますね。