├── .gitignore
├── .idea
├── misc.xml
├── vcs.xml
└── workspace.xml
├── README.md
├── README.zh.md
├── pom.xml
└── src
└── main
├── java
├── CommonConstant.java
├── ContractInteraction.java
├── EventListener.java
├── Signature.java
├── Transfer.java
└── Wallet.java
└── resources
└── Blog
├── ContractInteraction.md
├── ContractInteraction.zh.md
├── EventListener.md
├── EventListener.zh.md
├── Signature.md
├── Signature.zh.md
├── Transfer.md
├── Transfer.zh.md
├── Wallet.md
└── Wallet.zh.md
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 | !**/src/main/**/target/
4 | !**/src/test/**/target/
5 |
6 | ### IntelliJ IDEA ###
7 | .idea/
8 |
9 | ### Eclipse ###
10 | .apt_generated
11 | .classpath
12 | .factorypath
13 | .project
14 | .settings
15 | .springBeans
16 | .sts4-cache
17 |
18 | ### NetBeans ###
19 | /nbproject/private/
20 | /nbbuild/
21 | /dist/
22 | /nbdist/
23 | /.nb-gradle/
24 | build/
25 | !**/src/main/**/build/
26 | !**/src/test/**/build/
27 |
28 | ### VS Code ###
29 | .vscode/
30 |
31 | ### Mac OS ###
32 | .DS_Store
33 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
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 | "associatedIndex": 4
31 | }
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {
41 | "keyToString": {
42 | "RunOnceActivity.OpenProjectViewOnStart": "true",
43 | "RunOnceActivity.ShowReadmeOnStart": "true",
44 | "git-widget-placeholder": "master",
45 | "last_opened_file_path": "D:/coding/web3/code/web3j-eth-sample/src/main/resources/Blog"
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 | 1738374357910
107 |
108 |
109 | 1738374357910
110 |
111 |
112 |
113 | 1738376284982
114 |
115 |
116 |
117 | 1738376284982
118 |
119 |
120 |
121 | 1738386276063
122 |
123 |
124 |
125 | 1738386276063
126 |
127 |
128 |
129 | 1738386579498
130 |
131 |
132 |
133 | 1738386579498
134 |
135 |
136 |
137 | 1738386710555
138 |
139 |
140 |
141 | 1738386710555
142 |
143 |
144 |
145 | 1738390637346
146 |
147 |
148 |
149 | 1738390637346
150 |
151 |
152 |
153 | 1738397959951
154 |
155 |
156 |
157 | 1738397959951
158 |
159 |
160 |
161 | 1738401550189
162 |
163 |
164 |
165 | 1738401550189
166 |
167 |
168 |
169 | 1738460950483
170 |
171 |
172 |
173 | 1738460950483
174 |
175 |
176 |
177 | 1738472357324
178 |
179 |
180 |
181 | 1738472357324
182 |
183 |
184 |
185 | 1738671904098
186 |
187 |
188 |
189 | 1738671904098
190 |
191 |
192 |
193 | 1738756762028
194 |
195 |
196 |
197 | 1738756762028
198 |
199 |
200 |
201 | 1738756850281
202 |
203 |
204 |
205 | 1738756850281
206 |
207 |
208 |
209 | 1738757158451
210 |
211 |
212 |
213 | 1738757158451
214 |
215 |
216 |
217 | 1740790099360
218 |
219 |
220 |
221 | 1740790099360
222 |
223 |
224 |
225 | 1740790809117
226 |
227 |
228 |
229 | 1740790809117
230 |
231 |
232 |
233 | 1740801802889
234 |
235 |
236 |
237 | 1740801802889
238 |
239 |
240 |
241 | 1741176403698
242 |
243 |
244 |
245 | 1741176403698
246 |
247 |
248 |
249 | 1741177565030
250 |
251 |
252 |
253 | 1741177565030
254 |
255 |
256 |
257 | 1741604506959
258 |
259 |
260 |
261 | 1741604506959
262 |
263 |
264 |
265 | 1741693502184
266 |
267 |
268 |
269 | 1741693502184
270 |
271 |
272 |
273 | 1741693976368
274 |
275 |
276 |
277 | 1741693976368
278 |
279 |
280 |
281 | 1741694373344
282 |
283 |
284 |
285 | 1741694373344
286 |
287 |
288 |
289 | 1741694655096
290 |
291 |
292 |
293 | 1741694655096
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # web3j-eth-sample
2 |
3 | 🌍 **Languages:** [English](README.md) | [简体中文](README.zh.md)
4 |
5 | ## Project Introduction
6 | **web3j-eth-sample** is a Java-based sample project that demonstrates how to use the [web3j](https://github.com/hyperledger-web3j/web3j) library for Ethereum blockchain development. This example helps developers quickly understand how to integrate web3j into Java projects and perform common Ethereum operations.
7 |
8 | ## Technology Stack
9 | - **Java**
10 | - **web3j**
11 | - **Ethereum**
12 |
13 | ## Usage
14 | - Add web3j dependency
15 | Add the following dependencies to the `pom.xml` (Maven) file:
16 | ```xml
17 |
18 |
19 | org.web3j
20 | core
21 | 5.0.0
22 |
23 |
24 |
25 |
26 | org.apache.commons
27 | commons-lang3
28 | 3.12.0
29 |
30 | ```
31 | - Find the sample code that you need
32 | - Copy the code into your project
33 | - Configure network nodes (optional)
34 | - Run and test
35 |
36 | ## Sample List
37 | - Wallet Operations [Code](src/main/java/Wallet.java) [Doc](src/main/resources/Blog/Wallet.md)
38 | - Signature and Verification [Code](src/main/java/Signature.java) [Doc](src/main/resources/Blog/Signature.md)
39 | - Check Balance and Transfer ETH [Code](src/main/java/Transfer.java) [Doc](src/main/resources/Blog/Transfer.md)
40 | - Calling Ethereum Smart Contracts [Code](src/main/java/ContractInteraction.java) [Doc](src/main/resources/Blog/ContractInteraction.md)
41 | - Listening to Ethereum On-Chain Events [Code](src/main/java/EventListener.java) [Doc](src/main/resources/Blog/EventListener.md)
42 |
43 | ## Contribution Guidelines
44 | If you have any suggestions for improvements, feel free to submit an Issue or Pull Request.
45 |
46 | ## License
47 | Apache 2.0
--------------------------------------------------------------------------------
/README.zh.md:
--------------------------------------------------------------------------------
1 | # web3j-eth-sample
2 |
3 | 🌍 **Languages:** [English](README.md) | [简体中文](README.zh.md)
4 |
5 | ## 项目简介
6 | **web3j-eth-sample** 是一个基于 Java 的示例项目,展示如何使用 [web3j](https://github.com/hyperledger-web3j/web3j) 库进行以太坊区块链开发。通过该示例,开发者可以快速了解如何在 Java 项目中集成 web3j 并执行常见的以太坊操作。
7 |
8 | ## 技术栈
9 | - **Java**
10 | - **web3j**
11 | - **以太坊 (Ethereum)**
12 |
13 | ## 使用方法
14 | - 添加web3j依赖
15 | 在 `pom.xml`(Maven)文件中添加以下依赖项:
16 | ```xml
17 |
18 |
19 | org.web3j
20 | core
21 | 5.0.0
22 |
23 |
24 |
25 |
26 | org.apache.commons
27 | commons-lang3
28 | 3.12.0
29 |
30 | ```
31 | - 找到所需的功能或应用场景的示例代码
32 | - 复制代码至您的项目
33 | - 配置网络节点(可选)
34 | - 运行并测试
35 |
36 | ## 示例
37 | - 钱包操作 [代码](src/main/java/Wallet.java) [文档](src/main/resources/Blog/Wallet.zh.md)
38 | - 签名和验证 [Code](src/main/java/Signature.java) [Doc](src/main/resources/Blog/Signature.md)
39 | - 余额查询和发送ETH [Code](src/main/java/Transfer.java) [Doc](src/main/resources/Blog/Transfer.md)
40 | - 调用以太坊智能合约 [Code](src/main/java/ContractInteraction.java) [Doc](src/main/resources/Blog/ContractInteraction.md)
41 | - 监听以太坊链上事件 [Code](src/main/java/EventListener.java) [Doc](src/main/resources/Blog/EventListener.md)
42 |
43 | ## 贡献指南
44 | 如果您有任何改进建议,欢迎提交 Issue 或 Pull Request。
45 |
46 | ## 许可证
47 | Apache 2.0
48 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | org.example
5 | web3j-eth-sample
6 | 1.0-SNAPSHOT
7 | web3j-eth-sample
8 | http://maven.apache.org
9 |
10 |
11 |
12 |
13 | org.web3j
14 | core
15 | 5.0.0
16 |
17 |
18 |
19 |
20 | org.apache.commons
21 | commons-lang3
22 | 3.12.0
23 |
24 |
25 |
26 |
27 |
28 | org.apache.maven.plugins
29 | maven-compiler-plugin
30 |
31 | 8
32 | 8
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/main/java/CommonConstant.java:
--------------------------------------------------------------------------------
1 | public class CommonConstant {
2 | public static final int PRIVATE_KEY_RADIX = 16;
3 | public static final int SIGNATURE_BYTE_LENGTH = 65;
4 | public static final int V_INDEX = 64;
5 | public static final int V_BASE = 27;
6 | public static final int V_LOWER_BOUND = 27;
7 | public static final int R_START_INDEX = 0;
8 | public static final int R_END_INDEX = 32;
9 | public static final int S_START_INDEX = 32;
10 | public static final int S_END_INDEX = 64;
11 | public static final String ADDRESS_PREFIX = "0x";
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/ContractInteraction.java:
--------------------------------------------------------------------------------
1 | import org.web3j.abi.FunctionEncoder;
2 | import org.web3j.abi.TypeReference;
3 | import org.web3j.abi.datatypes.Function;
4 | import org.web3j.abi.datatypes.Type;
5 | import org.web3j.abi.datatypes.generated.Uint256;
6 | import org.web3j.crypto.Credentials;
7 | import org.web3j.protocol.Web3j;
8 | import org.web3j.protocol.core.DefaultBlockParameterName;
9 | import org.web3j.protocol.core.methods.request.Transaction;
10 | import org.web3j.protocol.core.methods.response.EthCall;
11 | import org.web3j.protocol.core.methods.response.EthChainId;
12 | import org.web3j.protocol.core.methods.response.EthSendTransaction;
13 | import org.web3j.protocol.http.HttpService;
14 | import org.web3j.tx.RawTransactionManager;
15 | import org.web3j.tx.gas.DefaultGasProvider;
16 |
17 | import java.io.IOException;
18 | import java.math.BigInteger;
19 | import java.util.ArrayList;
20 | import java.util.Arrays;
21 | import java.util.Collections;
22 | import java.util.List;
23 |
24 | public class ContractInteraction {
25 |
26 | private static final String RPC_URL = "RPC_URL"; // Test RPC URL, e.g., https://sepolia.optimism.io
27 | private static final String PRIVATE_KEY = "YOUR_PRIVATE_KEY";
28 | private static final String CONTRACT_ADDRESS = "CONTRACT_ADDRESS"; // Test contract address 0x833C27F4BFB4c1Eea93c747C3f5ECcf060c1B79d
29 |
30 | private static final Web3j web3j = Web3j.build(new HttpService(RPC_URL));
31 | private static final Credentials credentials = Credentials.create(PRIVATE_KEY);
32 |
33 | /**
34 | * Calls a view function of a smart contract (does not modify the blockchain state).
35 | *
36 | * @param functionName The name of the contract function to call.
37 | * @param inputParameters The input parameters required by the function.
38 | * @param outputParameters The expected output types of the function.
39 | * @return The raw response value from the contract call.
40 | * @throws IOException If an error occurs while executing the request.
41 | */
42 | public static String callContract(String functionName, List inputParameters, List> outputParameters) throws IOException {
43 | Function function = new Function(functionName, inputParameters, outputParameters);
44 | String encodedFunction = FunctionEncoder.encode(function);
45 |
46 | EthCall response = web3j.ethCall(
47 | Transaction.createEthCallTransaction(credentials.getAddress(), CONTRACT_ADDRESS, encodedFunction),
48 | DefaultBlockParameterName.LATEST
49 | ).send();
50 |
51 | return response.getValue();
52 | }
53 |
54 | /**
55 | * Sends a transaction to execute a function on the smart contract (modifies the blockchain state).
56 | *
57 | * @param functionName The name of the contract function to execute.
58 | * @param inputParameters The input parameters required by the function.
59 | * @param outputParameters The expected output types of the function (usually empty for transactions).
60 | * @return The transaction hash of the submitted transaction.
61 | * @throws Exception If an error occurs while sending the transaction.
62 | */
63 | public static String sendTransaction(String functionName, List inputParameters, List> outputParameters) throws Exception {
64 | // Construct the function call
65 | Function function = new Function(functionName, inputParameters, outputParameters);
66 | String encodedFunction = FunctionEncoder.encode(function);
67 |
68 | // Retrieve Chain ID
69 | EthChainId chainIdResponse = web3j.ethChainId().send();
70 | BigInteger chainId = chainIdResponse.getChainId();
71 |
72 | // Use RawTransactionManager to send the transaction
73 | RawTransactionManager transactionManager = new RawTransactionManager(web3j, credentials, chainId.longValue());
74 |
75 | EthSendTransaction transactionResponse = transactionManager.sendTransaction(
76 | DefaultGasProvider.GAS_PRICE,
77 | DefaultGasProvider.GAS_LIMIT,
78 | CONTRACT_ADDRESS,
79 | encodedFunction,
80 | BigInteger.ZERO
81 | );
82 |
83 | // Check for transaction errors
84 | if (transactionResponse.hasError()) {
85 | throw new RuntimeException("Error sending transaction: " + transactionResponse.getError().getMessage());
86 | }
87 |
88 | return transactionResponse.getTransactionHash();
89 | }
90 |
91 | public static void main(String[] args) throws Exception {
92 | // Retrieve initial contract value
93 | String resultBefore = callContract("getValue",
94 | Collections.emptyList(),
95 | Arrays.asList(new TypeReference() {}));
96 | System.out.println("Value before transaction: " + resultBefore);
97 |
98 | // Construct input parameters (choose the appropriate data type from org.web3j.abi.datatypes)
99 | List inputParameters = new ArrayList<>();
100 | Uint256 value = new Uint256(BigInteger.valueOf(6));
101 | inputParameters.add(value);
102 |
103 | // Send transaction to modify the contract state
104 | String txHash = sendTransaction("setValue",
105 | inputParameters,
106 | Collections.emptyList());
107 | System.out.println("Transaction sent! Tx Hash: " + txHash);
108 |
109 | // Wait for the transaction to be confirmed (you can also check manually on a blockchain explorer)
110 | Thread.sleep(15000); // 15 seconds wait time
111 |
112 | // Retrieve contract value after the transaction
113 | String resultAfter = callContract("getValue",
114 | Collections.emptyList(),
115 | Arrays.asList(new TypeReference() {}));
116 | System.out.println("Value after transaction: " + resultAfter);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/EventListener.java:
--------------------------------------------------------------------------------
1 | import io.reactivex.Flowable;
2 | import org.web3j.abi.EventEncoder;
3 | import org.web3j.abi.FunctionReturnDecoder;
4 | import org.web3j.abi.TypeReference;
5 | import org.web3j.abi.datatypes.Address;
6 | import org.web3j.abi.datatypes.Event;
7 | import org.web3j.abi.datatypes.Type;
8 | import org.web3j.abi.datatypes.generated.Uint256;
9 | import org.web3j.protocol.Web3j;
10 | import org.web3j.protocol.core.DefaultBlockParameter;
11 | import org.web3j.protocol.core.DefaultBlockParameterName;
12 | import org.web3j.protocol.core.methods.request.EthFilter;
13 | import org.web3j.protocol.core.methods.response.Log;
14 | import org.web3j.protocol.http.HttpService;
15 |
16 | import java.math.BigInteger;
17 | import java.util.Arrays;
18 | import java.util.List;
19 |
20 | public class EventListener {
21 |
22 | private static final String RPC_URL = "RPC_URL";
23 | // Since most public rpc node has block the event api, so you should set your own rpc node url.
24 | // You can get a free usage rpc node from https://dashboard.alchemy.com/
25 |
26 | private static final String CONTRACT_ADDRESS = "CONTRACT_ADDRESS"; // Test contract address 0x833C27F4BFB4c1Eea93c747C3f5ECcf060c1B79d
27 |
28 | public static final Event VALUE_UPDATED = new Event(
29 | "ValueUpdated",
30 | Arrays.asList(
31 | new TypeReference(true) {}, // indexed `updater`
32 | new TypeReference() {}, // oldValue (non-indexed)
33 | new TypeReference() {} // newValue (non-indexed)
34 | )
35 | );
36 |
37 | private static final Web3j web3j = Web3j.build(new HttpService(RPC_URL));
38 |
39 | public void startLogListening() {
40 | DefaultBlockParameter startBlock = DefaultBlockParameterName.EARLIEST;
41 | // If you want to start from a specific block, you can set the block number as below
42 | // startBlock = DefaultBlockParameter.valueOf(blockNumber);
43 |
44 | EthFilter filter = new EthFilter(
45 | startBlock,
46 | DefaultBlockParameterName.LATEST, // You can change this value to set the latest block you want to listen to
47 | CONTRACT_ADDRESS
48 | );
49 |
50 | // Add event encoding to the filter
51 | filter.addOptionalTopics(
52 | EventEncoder.encode(VALUE_UPDATED)
53 | );
54 |
55 | // Listen to event logs
56 | Flowable logFlowable = web3j.ethLogFlowable(filter);
57 |
58 | logFlowable.subscribe(
59 | log -> {
60 | String eventSignature = log.getTopics().get(0);
61 | if (eventSignature.equals(EventEncoder.encode(VALUE_UPDATED))) {
62 | valueUpdated(log);
63 | }
64 | },
65 | throwable -> {
66 | System.err.println("Error processing event log: " + throwable.getMessage());
67 | }
68 | );
69 | }
70 |
71 | private void valueUpdated(Log log) {
72 | // Ensure log data is not empty
73 | if (log.getData() == null || log.getData().equals("0x")) {
74 | System.err.println("Log data is empty! Skipping this event.");
75 | return;
76 | }
77 |
78 | // Retrieve `updater` (indexed parameter stored in topics[1])
79 | String updater = "0x" + log.getTopics().get(1).substring(26); // Extract the last 40 characters of the address
80 |
81 | // Decode non-indexed parameters `oldValue` and `newValue`
82 | List decoded = FunctionReturnDecoder.decode(log.getData(), VALUE_UPDATED.getNonIndexedParameters());
83 |
84 | if (decoded.size() < 2) {
85 | System.err.println("Decoded data size is incorrect!");
86 | return;
87 | }
88 |
89 | int oldValue = ((BigInteger) decoded.get(0).getValue()).intValue();
90 | int newValue = ((BigInteger) decoded.get(1).getValue()).intValue();
91 |
92 | System.out.println("Value updated by " + updater + ", old: " + oldValue + ", new: " + newValue);
93 | }
94 |
95 | public static void main(String[] args) {
96 | EventListener eventListener = new EventListener();
97 | eventListener.startLogListening();
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/Signature.java:
--------------------------------------------------------------------------------
1 | import org.apache.commons.lang3.StringUtils;
2 | import org.web3j.crypto.ECKeyPair;
3 | import org.web3j.crypto.Keys;
4 | import org.web3j.crypto.Sign;
5 | import org.web3j.utils.Numeric;
6 |
7 | import java.math.BigInteger;
8 | import java.security.SignatureException;
9 | import java.util.Arrays;
10 |
11 | public class Signature {
12 | /**
13 | * Validates a given signature against the specified message and wallet address.
14 | *
15 | * @param signature The digital signature to validate, in hexadecimal format.
16 | * @param message The message (original message) that was signed.
17 | * @param walletAddress The wallet address expected to match the signature.
18 | * @return True if the signature is valid and corresponds to the wallet
19 | * address; false otherwise.
20 | */
21 | public static Boolean isSignatureValid(String signature, String message, String walletAddress) {
22 | if (StringUtils.isAnyBlank(signature, message, walletAddress)) {
23 | return false;
24 | }
25 |
26 | byte[] signatureBytes = Numeric.hexStringToByteArray(signature);
27 | if (signatureBytes.length != CommonConstant.SIGNATURE_BYTE_LENGTH) {
28 | return false;
29 | }
30 |
31 | byte v = signatureBytes[CommonConstant.V_INDEX];
32 | if (v < CommonConstant.V_LOWER_BOUND) {
33 | v += CommonConstant.V_BASE;
34 | }
35 | Sign.SignatureData signatureData = new Sign.SignatureData(
36 | v,
37 | Arrays.copyOfRange(signatureBytes, CommonConstant.R_START_INDEX, CommonConstant.R_END_INDEX),
38 | Arrays.copyOfRange(signatureBytes, CommonConstant.S_START_INDEX, CommonConstant.S_END_INDEX)
39 | );
40 | BigInteger publicKey;
41 | try {
42 | publicKey = Sign.signedPrefixedMessageToKey(message.getBytes(), signatureData);
43 | } catch (SignatureException e) {
44 | return false;
45 | }
46 | String parsedAddress = CommonConstant.ADDRESS_PREFIX + Keys.getAddress(publicKey);
47 | return parsedAddress.equalsIgnoreCase(walletAddress);
48 | }
49 |
50 | /**
51 | * Signs a message using a given private key and returns the signature in hexadecimal format.
52 | * This function applies the Ethereum-specific prefix to the message before signing.
53 | *
54 | * @param privateKeyHex The private key in hexadecimal format.
55 | * @param message The message to be signed.
56 | * @return The generated signature as a hexadecimal string.
57 | */
58 | public static String signPrefixedMessage(String privateKeyHex, String message) {
59 | BigInteger privateKey = new BigInteger(privateKeyHex, CommonConstant.PRIVATE_KEY_RADIX);
60 |
61 | ECKeyPair keyPair = ECKeyPair.create(privateKey);
62 | Sign.SignatureData signatureData = Sign.signPrefixedMessage(message.getBytes(), keyPair);
63 |
64 | return Numeric.toHexStringNoPrefix(signatureData.getR()) +
65 | Numeric.toHexStringNoPrefix(signatureData.getS()) +
66 | Numeric.toHexStringNoPrefix(signatureData.getV());
67 | }
68 |
69 | public static void main(String[] args) {
70 | // Define the private key and wallet address, change to your own
71 | String privateKeyHex = "ad6bfebb055780013c24afdd167cceb96ef89ddf9e4eb8615a99e573531407b5";
72 | String walletAddress = "0x4dc2739b3de594754066357e54bfce70167b3f99";
73 |
74 | // Define the message to be signed
75 | String message = "2778f9e5-5992-4b06-8a8f-85135d687cff";
76 |
77 | // Sign the message using the private key
78 | String signature = signPrefixedMessage(privateKeyHex, message);
79 |
80 | // Validate the signature
81 | boolean isValid = isSignatureValid(signature, message, walletAddress);
82 |
83 | // Print the results
84 | System.out.println("Signature: " + signature);
85 | System.out.println("Is signature valid: " + isValid);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/Transfer.java:
--------------------------------------------------------------------------------
1 | import org.web3j.crypto.Credentials;
2 | import org.web3j.crypto.RawTransaction;
3 | import org.web3j.crypto.TransactionEncoder;
4 | import org.web3j.protocol.Web3j;
5 | import org.web3j.protocol.core.DefaultBlockParameterName;
6 | import org.web3j.protocol.core.methods.response.EthChainId;
7 | import org.web3j.protocol.core.methods.response.EthGasPrice;
8 | import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
9 | import org.web3j.protocol.core.methods.response.EthSendTransaction;
10 | import org.web3j.protocol.http.HttpService;
11 | import org.web3j.utils.Convert;
12 | import org.web3j.utils.Numeric;
13 |
14 | import java.io.IOException;
15 | import java.math.BigDecimal;
16 | import java.math.BigInteger;
17 |
18 | public class Transfer {
19 |
20 | private static final String RPC_URL = "RPC_URL"; // Test RPC URL, e.g., https://sepolia.optimism.io
21 | private static final Web3j web3j = Web3j.build(new HttpService(RPC_URL));
22 |
23 | /**
24 | * Retrieves the Ether balance of the given Ethereum address.
25 | *
26 | * @param address The Ethereum address whose balance is to be retrieved.
27 | * @return The balance in Ether as a BigDecimal.
28 | * @throws IOException If there is an issue communicating with the Ethereum node.
29 | */
30 | public static BigDecimal getETHBalance(String address) throws IOException {
31 | // Retrieve balance in Wei
32 | BigInteger balanceInWei = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send().getBalance();
33 | // Convert Wei to Ether
34 | return Convert.fromWei(new BigDecimal(balanceInWei), Convert.Unit.ETHER);
35 | }
36 |
37 | /**
38 | * Transfers Ether from a sender's account to a recipient's account.
39 | *
40 | * @param senderPrivateKey The private key of the sender's Ethereum account (keep this secure).
41 | * @param recipientAddress The recipient's Ethereum address.
42 | * @param amountInEther The amount to transfer in Ether.
43 | * @return The transaction hash if the transfer is successful; otherwise, returns null.
44 | * @throws IOException If there is an issue communicating with the Ethereum node.
45 | */
46 | public static String transfer(String senderPrivateKey, String recipientAddress, BigDecimal amountInEther) throws IOException {
47 | // Retrieve the chain ID of the connected network
48 | EthChainId chainIdResponse = web3j.ethChainId().send();
49 | long chainId = chainIdResponse.getChainId().longValue();
50 |
51 | /* Sender Account Configuration */
52 | // Create credentials from the sender's private key
53 | Credentials credentials = Credentials.create(senderPrivateKey);
54 | String senderAddress = credentials.getAddress();
55 | // Get the current nonce for the sender's account
56 | EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
57 | senderAddress, DefaultBlockParameterName.LATEST).send();
58 | BigInteger nonce = ethGetTransactionCount.getTransactionCount();
59 |
60 | /* Transaction Amount and Fee Configuration */
61 | // Convert the amount from Ether to Wei (1 ETH = 10^18 Wei)
62 | BigInteger value = Convert.toWei(amountInEther, Convert.Unit.ETHER).toBigInteger();
63 | // Get the current Gas price
64 | EthGasPrice ethGasPrice = web3j.ethGasPrice().send();
65 | BigInteger gasPrice = ethGasPrice.getGasPrice();
66 | // Gas limit for a standard Ether transfer (typically 21,000)
67 | BigInteger gasLimit = BigInteger.valueOf(21000);
68 |
69 | /* Transfer */
70 | // Create the transaction object
71 | RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
72 | nonce, gasPrice, gasLimit, recipientAddress, value);
73 | // Sign the transaction
74 | byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);
75 | String hexValue = Numeric.toHexString(signedMessage);
76 | // Send the transaction
77 | EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
78 | // Return the transaction hash if successful, or null if the transaction failed
79 | return ethSendTransaction.getTransactionHash();
80 | }
81 |
82 | public static void main(String[] args) throws Exception {
83 | // The private key of the sender's Ethereum account (keep this private and secure)
84 | String privateKey = "YOUR_PRIVATE_KEY";
85 | // The recipient's Ethereum address
86 | String recipientAddress = "YOUR_RECIPIENT_ADDRESS";
87 | // The transfer amount in Ether
88 | BigDecimal amountInEther = new BigDecimal("0.001");
89 |
90 | //Balance of recipient address before transfer
91 | BigDecimal before = getETHBalance(recipientAddress);
92 | System.out.println("Balance of recipient address before transfer: " + before);
93 |
94 | // Perform the transfer
95 | String transactionHash = Transfer.transfer(privateKey, recipientAddress, amountInEther);
96 | System.out.println("Transaction Hash: " + transactionHash);
97 |
98 | // Wait for the transaction to be confirmed (you can also check manually on a blockchain explorer)
99 | Thread.sleep(15000); // 15 seconds wait time
100 |
101 | //Balance of recipient address before transfer
102 | BigDecimal after = getETHBalance(recipientAddress);
103 | System.out.println("Balance of recipient address after transfer: " + after);
104 | }
105 | }
--------------------------------------------------------------------------------
/src/main/java/Wallet.java:
--------------------------------------------------------------------------------
1 | import org.web3j.crypto.ECKeyPair;
2 | import org.web3j.crypto.Keys;
3 | import org.web3j.crypto.WalletUtils;
4 |
5 | import java.math.BigInteger;
6 | import java.security.InvalidAlgorithmParameterException;
7 | import java.security.NoSuchAlgorithmException;
8 | import java.security.NoSuchProviderException;
9 |
10 | public class Wallet {
11 |
12 | /**
13 | * Generates a random Ethereum private key.
14 | *
15 | * @return A randomly generated private key in hexadecimal format.
16 | * @throws InvalidAlgorithmParameterException If the cryptographic algorithm parameters are invalid.
17 | * @throws NoSuchAlgorithmException If the cryptographic algorithm is not available.
18 | * @throws NoSuchProviderException If the security provider is not available.
19 | */
20 | public static String createRandomPrivateKey() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {
21 | // Generate a random ECKeyPair (contains both private and public keys)
22 | ECKeyPair ecKeyPair = Keys.createEcKeyPair();
23 | // Return the private key as a hexadecimal string
24 | return ecKeyPair.getPrivateKey().toString(CommonConstant.PRIVATE_KEY_RADIX);
25 | }
26 |
27 | /**
28 | * Generates an Ethereum wallet address from a given private key.
29 | *
30 | * @param privateKeyHex The private key in hexadecimal format.
31 | * @return The generated Ethereum wallet address (starting with "0x").
32 | */
33 | public static String getWalletAddressFromPrivateKeyHex(String privateKeyHex) {
34 | // Convert the private key from hexadecimal format to BigInteger
35 | BigInteger privateKey = new BigInteger(privateKeyHex, CommonConstant.PRIVATE_KEY_RADIX);
36 | // Create an ECKeyPair object (contains both private and public keys)
37 | ECKeyPair keyPair = ECKeyPair.create(privateKey);
38 | // Generate a wallet address from the public key and return it with "0x" prefix
39 | return CommonConstant.ADDRESS_PREFIX + Keys.getAddress(keyPair.getPublicKey());
40 | }
41 |
42 | /**
43 | * Validates whether a given Ethereum wallet address is in a correct format.
44 | *
45 | * @param address The Ethereum wallet address (should start with "0x").
46 | * @return True if the address is valid, false otherwise.
47 | */
48 | public static boolean isValidAddress(String address) {
49 | return WalletUtils.isValidAddress(address);
50 | }
51 |
52 | /**
53 | * Validates whether a given private key is valid.
54 | *
55 | * @param privateKey The private key in hexadecimal format.
56 | * @return True if the private key is valid, false otherwise.
57 | */
58 | public static boolean isValidPrivateKey(String privateKey) {
59 | return WalletUtils.isValidPrivateKey(privateKey);
60 | }
61 |
62 | public static void main(String[] args) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {
63 | // Generate a random private key
64 | String privateKeyHex = Wallet.createRandomPrivateKey();
65 |
66 | // Generate a wallet address from the private key
67 | String walletAddress = Wallet.getWalletAddressFromPrivateKeyHex(privateKeyHex);
68 |
69 | // Print the generated private key and wallet address
70 | System.out.println("The wallet address of [" + privateKeyHex + "] is: [" + walletAddress + "]");
71 |
72 | // Validate if the generated wallet address is correct
73 | System.out.println("The wallet address [" + walletAddress + "] is " + (Wallet.isValidAddress(walletAddress) ? "valid" : "not valid"));
74 |
75 | // Validate if the generated private key is correct
76 | System.out.println("The private key [" + privateKeyHex + "] is " + (Wallet.isValidPrivateKey(privateKeyHex) ? "valid" : "not valid"));
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/resources/Blog/ContractInteraction.md:
--------------------------------------------------------------------------------
1 | # Calling Ethereum Smart Contracts in Java
2 |
3 | 🌍 **Languages:** [English](ContractInteraction.md) | [简体中文](ContractInteraction.zh.md)
4 |
5 | Web3j is a powerful tool, but it can be somewhat complex to use. Therefore, I have provided some usage examples to help you get started.
6 |
7 | Complete example code repository: [web3j-eth-sample](https://github.com/zhoujingweb3/web3j-eth-sample)
8 |
9 | This article provides examples of using Web3j in Java to interact with Ethereum smart contracts.
10 |
11 | ## Dependencies
12 | ### Maven Dependency
13 | ```xml
14 |
15 |
16 | org.web3j
17 | core
18 | 5.0.0
19 |
20 | ```
21 |
22 | ### Test Smart Contract Code: `TestContract.sol`
23 | ```solidity
24 | // SPDX-License-Identifier: MIT
25 | pragma solidity ^0.8.0;
26 |
27 | contract TestContract {
28 | uint256 private value;
29 |
30 | event ValueUpdated(address indexed updater, uint256 oldValue, uint256 newValue);
31 |
32 | // Set initial value
33 | constructor(uint256 _initialValue) {
34 | value = _initialValue;
35 | }
36 |
37 | // Read the value (view, read-only)
38 | function getValue() public view returns (uint256) {
39 | return value;
40 | }
41 |
42 | // Update the value (requires transaction & gas)
43 | function setValue(uint256 _newValue) public {
44 | uint256 oldValue = value;
45 | value = _newValue;
46 | emit ValueUpdated(msg.sender, oldValue, _newValue);
47 | }
48 | }
49 | ```
50 | This test contract contains:
51 | - A `uint256` variable `value`.
52 | - A `getValue` function to retrieve the `value`.
53 | - A `setValue` function to modify the `value`.
54 |
55 | I have deployed this test contract on the Optimism Sepolia network at contract address: `0x833C27F4BFB4c1Eea93c747C3f5ECcf060c1B79d`. You can use it if needed. The `RPC_URL` parameter can also be set to a public RPC node URL, such as `https://sepolia.optimism.io`.
56 |
57 | ## Examples
58 | ### Calling a Read-Only Function (Fetching On-Chain Data Without Gas)
59 | ```java
60 | public static String callContract(String functionName, List inputParameters, List> outputParameters) throws IOException {
61 | Function function = new Function(functionName, inputParameters, outputParameters);
62 | String encodedFunction = FunctionEncoder.encode(function);
63 |
64 | EthCall response = web3j.ethCall(
65 | Transaction.createEthCallTransaction(credentials.getAddress(), CONTRACT_ADDRESS, encodedFunction),
66 | DefaultBlockParameterName.LATEST
67 | ).send();
68 |
69 | return response.getValue();
70 | }
71 | ```
72 | ### Calling a Write Function (Modifying On-Chain Data, Requires Gas)
73 | ```java
74 | public static String sendTransaction(String functionName, List inputParameters, List> outputParameters) throws Exception {
75 | // Construct the function call
76 | Function function = new Function(functionName, inputParameters, outputParameters);
77 | String encodedFunction = FunctionEncoder.encode(function);
78 |
79 | // Retrieve Chain ID
80 | EthChainId chainIdResponse = web3j.ethChainId().send();
81 | BigInteger chainId = chainIdResponse.getChainId();
82 |
83 | // Use RawTransactionManager to send the transaction
84 | RawTransactionManager transactionManager = new RawTransactionManager(web3j, credentials, chainId.longValue());
85 |
86 | EthSendTransaction transactionResponse = transactionManager.sendTransaction(
87 | DefaultGasProvider.GAS_PRICE,
88 | DefaultGasProvider.GAS_LIMIT,
89 | CONTRACT_ADDRESS,
90 | encodedFunction,
91 | BigInteger.ZERO
92 | );
93 |
94 | // Check for transaction errors
95 | if (transactionResponse.hasError()) {
96 | throw new RuntimeException("Error sending transaction: " + transactionResponse.getError().getMessage());
97 | }
98 |
99 | return transactionResponse.getTransactionHash();
100 | }
101 | ```
102 |
103 | ## Example Code
104 | [ContractInteraction](../../java/ContractInteraction.java)
105 |
106 | ### Test Steps in the `main` Function
107 | In the provided example, the following steps are executed:
108 | 1. Call the `getValue` function of the contract to read the current `value`.
109 | 2. Call the `setValue` function of the contract to update the `value`.
110 | 3. Wait for `15 seconds`.
111 | 4. Call `getValue` again to confirm the update was successful.
112 |
113 | That's it!
--------------------------------------------------------------------------------
/src/main/resources/Blog/ContractInteraction.zh.md:
--------------------------------------------------------------------------------
1 | # Java中调用以太坊智能合约
2 |
3 | 🌍 **Languages:** [English](ContractInteraction.md) | [简体中文](ContractInteraction.zh.md)
4 |
5 | Web3j是一个很好用的工具,但是使用起来有点复杂,因此我写了一些使用示例,希望可以帮到各位。
6 | 完整示例代码仓库地址:[web3j-eth-sample](https://github.com/zhoujingweb3/web3j-eth-sample)
7 | 本章内容是关于使用Web3j在Java中调用以太坊智能合约的示例。
8 | ## 依赖
9 | ### Maven依赖
10 | ```java
11 |
12 |
13 | org.web3j
14 | core
15 | 5.0.0
16 |
17 |
18 | ```
19 | ### 测试合约代码TestContract.sol
20 |
21 | ```java
22 | // SPDX-License-Identifier: MIT
23 | pragma solidity ^0.8.0;
24 |
25 | contract TestContract {
26 | uint256 private value;
27 |
28 | event ValueUpdated(address indexed updater, uint256 oldValue, uint256 newValue);
29 |
30 | // 设置初始值
31 | constructor(uint256 _initialValue) {
32 | value = _initialValue;
33 | }
34 |
35 | // 读取数值 (view, 只读查询)
36 | function getValue() public view returns (uint256) {
37 | return value;
38 | }
39 |
40 | // 修改数值 (需要交易 & Gas)
41 | function setValue(uint256 _newValue) public {
42 | uint256 oldValue = value;
43 | value = _newValue;
44 | emit ValueUpdated(msg.sender, oldValue, _newValue);
45 | }
46 | }
47 |
48 | ```
49 | 这个测试合约包含了一个uint256形value变量,一个getValue函数获取value变量的值,一个setValue函数设置value变量的值。
50 | 我在optimism链的sepolia网络部署了该测试合约,合约地址为:0x833C27F4BFB4c1Eea93c747C3f5ECcf060c1B79d,有需要的话可以直接使用,RPC_URL参数也可以用公用的RPC节点URL,例如:https://sepolia.optimism.io
51 | ## 示例
52 | ### 调用只读函数(只获取链上数据,不需要Gas)
53 | ```java
54 | public static String callContract(String functionName, List inputParameters, List> outputParameters) throws IOException {
55 | Function function = new Function(functionName, inputParameters, outputParameters);
56 | String encodedFunction = FunctionEncoder.encode(function);
57 |
58 | EthCall response = web3j.ethCall(
59 | Transaction.createEthCallTransaction(credentials.getAddress(), CONTRACT_ADDRESS, encodedFunction),
60 | DefaultBlockParameterName.LATEST
61 | ).send();
62 |
63 | return response.getValue();
64 | }
65 | ```
66 | ### 调用写入函数(写入或更改链上数据,需要Gas)
67 | ```java
68 | public static String sendTransaction(String functionName, List inputParameters, List> outputParameters) throws Exception {
69 | // Construct the function call
70 | Function function = new Function(functionName, inputParameters, outputParameters);
71 | String encodedFunction = FunctionEncoder.encode(function);
72 |
73 | // Retrieve Chain ID
74 | EthChainId chainIdResponse = web3j.ethChainId().send();
75 | BigInteger chainId = chainIdResponse.getChainId();
76 |
77 | // Use RawTransactionManager to send the transaction
78 | RawTransactionManager transactionManager = new RawTransactionManager(web3j, credentials, chainId.longValue());
79 |
80 | EthSendTransaction transactionResponse = transactionManager.sendTransaction(
81 | DefaultGasProvider.GAS_PRICE,
82 | DefaultGasProvider.GAS_LIMIT,
83 | CONTRACT_ADDRESS,
84 | encodedFunction,
85 | BigInteger.ZERO
86 | );
87 |
88 | // Check for transaction errors
89 | if (transactionResponse.hasError()) {
90 | throw new RuntimeException("Error sending transaction: " + transactionResponse.getError().getMessage());
91 | }
92 |
93 | return transactionResponse.getTransactionHash();
94 | }
95 | ```
96 | ## 示例代码
97 | [ContractInteraction](../../java/ContractInteraction.java)
98 |
99 | 在示例代码的Main函数中,我们实现了以下测试步骤:
100 |
101 | 1. 调用合约的getValue函数,读取 Value 的值
102 | 2. 调用合约的setValue函数,更改 Value 的值
103 | 3. 等待15秒中后,再次调用合约的getValue函数,读取 Value 的值,确认修改成功
104 |
105 | 以上。
--------------------------------------------------------------------------------
/src/main/resources/Blog/EventListener.md:
--------------------------------------------------------------------------------
1 | # Listening to Ethereum On-Chain Events in Java
2 |
3 | 🌍 **Languages:** [English](EventListener.md) | [简体中文](EventListener.zh.md)
4 |
5 | Web3j is a powerful tool, but it can be somewhat complex to use. Therefore, I have provided some usage examples to help you get started.
6 |
7 | Complete example code repository: [web3j-eth-sample](https://github.com/zhoujingweb3/web3j-eth-sample)
8 |
9 | This article provides an example of how to use Web3j in Java to listen to Ethereum smart contract events.
10 |
11 | ## Dependencies
12 | ### Maven Dependency
13 | ```xml
14 |
15 |
16 | org.web3j
17 | core
18 | 5.0.0
19 |
20 | ```
21 |
22 | ### Test Smart Contract Code: `TestContract.sol`
23 | ```solidity
24 | // SPDX-License-Identifier: MIT
25 | pragma solidity ^0.8.0;
26 |
27 | contract TestContract {
28 | uint256 private value;
29 |
30 | event ValueUpdated(address indexed updater, uint256 oldValue, uint256 newValue);
31 |
32 | // Set initial value
33 | constructor(uint256 _initialValue) {
34 | value = _initialValue;
35 | }
36 |
37 | // Read the value (view, read-only)
38 | function getValue() public view returns (uint256) {
39 | return value;
40 | }
41 |
42 | // Update the value (requires transaction & gas)
43 | function setValue(uint256 _newValue) public {
44 | uint256 oldValue = value;
45 | value = _newValue;
46 | emit ValueUpdated(msg.sender, oldValue, _newValue);
47 | }
48 | }
49 | ```
50 | This test contract contains:
51 | - A `uint256` variable `value`.
52 | - A `getValue` function to retrieve the `value`.
53 | - A `setValue` function to modify the `value` and emit an event.
54 |
55 | I have deployed this test contract on the Optimism Sepolia network at contract address: `0x833C27F4BFB4c1Eea93c747C3f5ECcf060c1B79d`. You can use it if needed. Since most public nodes block event listening APIs, you need a private RPC node. I recommend using [Alchemy](https://dashboard.alchemy.com/) to obtain a free private RPC node.
56 |
57 | ## Example
58 | ### Complete Code: `EventListener.java`
59 | [EventListener](../../java/EventListener.java)
60 |
61 | ```java
62 | import io.reactivex.Flowable;
63 | import org.web3j.abi.EventEncoder;
64 | import org.web3j.abi.FunctionReturnDecoder;
65 | import org.web3j.abi.TypeReference;
66 | import org.web3j.abi.datatypes.Address;
67 | import org.web3j.abi.datatypes.Event;
68 | import org.web3j.abi.datatypes.Type;
69 | import org.web3j.abi.datatypes.generated.Uint256;
70 | import org.web3j.protocol.Web3j;
71 | import org.web3j.protocol.core.DefaultBlockParameter;
72 | import org.web3j.protocol.core.DefaultBlockParameterName;
73 | import org.web3j.protocol.core.methods.request.EthFilter;
74 | import org.web3j.protocol.core.methods.response.Log;
75 | import org.web3j.protocol.http.HttpService;
76 |
77 | import java.math.BigInteger;
78 | import java.util.Arrays;
79 | import java.util.List;
80 |
81 | public class EventListener {
82 |
83 | private static final String RPC_URL = "RPC_URL";
84 | private static final String CONTRACT_ADDRESS = "CONTRACT_ADDRESS";
85 |
86 | public static final Event VALUE_UPDATED = new Event(
87 | "ValueUpdated",
88 | Arrays.asList(
89 | new TypeReference(true) {}, // indexed `updater`
90 | new TypeReference() {}, // oldValue (non-indexed)
91 | new TypeReference() {} // newValue (non-indexed)
92 | )
93 | );
94 |
95 | private static final Web3j web3j = Web3j.build(new HttpService(RPC_URL));
96 |
97 | public void startLogListening() {
98 | DefaultBlockParameter startBlock = DefaultBlockParameterName.EARLIEST;
99 |
100 | EthFilter filter = new EthFilter(
101 | startBlock,
102 | DefaultBlockParameterName.LATEST,
103 | CONTRACT_ADDRESS
104 | );
105 |
106 | filter.addOptionalTopics(
107 | EventEncoder.encode(VALUE_UPDATED)
108 | );
109 |
110 | Flowable logFlowable = web3j.ethLogFlowable(filter);
111 |
112 | logFlowable.subscribe(
113 | log -> {
114 | String eventSignature = log.getTopics().get(0);
115 | if (eventSignature.equals(EventEncoder.encode(VALUE_UPDATED))) {
116 | valueUpdated(log);
117 | }
118 | },
119 | throwable -> {
120 | System.err.println("Error processing event log: " + throwable.getMessage());
121 | }
122 | );
123 | }
124 |
125 | private void valueUpdated(Log log) {
126 | if (log.getData() == null || log.getData().equals("0x")) {
127 | System.err.println("Log data is empty! Skipping this event.");
128 | return;
129 | }
130 |
131 | String updater = "0x" + log.getTopics().get(1).substring(26);
132 | List decoded = FunctionReturnDecoder.decode(log.getData(), VALUE_UPDATED.getNonIndexedParameters());
133 |
134 | if (decoded.size() < 2) {
135 | System.err.println("Decoded data size is incorrect!");
136 | return;
137 | }
138 |
139 | int oldValue = ((BigInteger) decoded.get(0).getValue()).intValue();
140 | int newValue = ((BigInteger) decoded.get(1).getValue()).intValue();
141 |
142 | System.out.println("Value updated by " + updater + ", old: " + oldValue + ", new: " + newValue);
143 | }
144 |
145 | public static void main(String[] args) {
146 | EventListener eventListener = new EventListener();
147 | eventListener.startLogListening();
148 | }
149 | }
150 | ```
151 |
152 | ### Key Implementation Details
153 | #### Defining the Event Listener
154 | The event in the contract is defined as:
155 | ```solidity
156 | event ValueUpdated(address indexed updater, uint256 oldValue, uint256 newValue);
157 | ```
158 | Since `updater` is an `indexed` parameter, we must explicitly mark it as `true` in the event listener:
159 | ```java
160 | public static final Event VALUE_UPDATED = new Event(
161 | "ValueUpdated",
162 | Arrays.asList(
163 | new TypeReference(true) {},
164 | new TypeReference() {},
165 | new TypeReference() {}
166 | )
167 | );
168 | ```
169 |
170 | #### Setting Up the Event Filter
171 | You can configure the block range to listen for events. The example code listens from `EARLIEST` to `LATEST`, but you can set a specific block number:
172 | ```java
173 | DefaultBlockParameter startBlock = DefaultBlockParameterName.EARLIEST;
174 | EthFilter filter = new EthFilter(
175 | startBlock,
176 | DefaultBlockParameterName.LATEST,
177 | CONTRACT_ADDRESS
178 | );
179 | filter.addOptionalTopics(
180 | EventEncoder.encode(VALUE_UPDATED)
181 | );
182 | ```
183 |
184 | #### Listening for Events
185 | ```java
186 | Flowable logFlowable = web3j.ethLogFlowable(filter);
187 | logFlowable.subscribe(
188 | log -> {
189 | String eventSignature = log.getTopics().get(0);
190 | if (eventSignature.equals(EventEncoder.encode(VALUE_UPDATED))) {
191 | valueUpdated(log);
192 | }
193 | },
194 | throwable -> {
195 | System.err.println("Error processing event log: " + throwable.getMessage());
196 | }
197 | );
198 | ```
199 |
200 | That’s it!
--------------------------------------------------------------------------------
/src/main/resources/Blog/EventListener.zh.md:
--------------------------------------------------------------------------------
1 | # Java中监听以太坊链上事件
2 |
3 | 🌍 **Languages:** [English](EventListener.md) | [简体中文](EventListener.zh.md)
4 |
5 | Web3j是一个很好用的工具,但是使用起来有点复杂,因此我写了一些使用示例,希望可以帮到各位。
6 | 完整示例代码仓库地址:[web3j-eth-sample](https://github.com/zhoujingweb3/web3j-eth-sample)
7 | 本章内容是关于使用Web3j在Java中调用以太坊智能合约的示例。
8 | ## 依赖
9 | ### Maven依赖
10 | ```java
11 |
12 |
13 | org.web3j
14 | core
15 | 5.0.0
16 |
17 |
18 | ```
19 | ### 测试合约代码TestContract.sol
20 |
21 | ```java
22 | // SPDX-License-Identifier: MIT
23 | pragma solidity ^0.8.0;
24 |
25 | contract TestContract {
26 | uint256 private value;
27 |
28 | event ValueUpdated(address indexed updater, uint256 oldValue, uint256 newValue);
29 |
30 | // 设置初始值
31 | constructor(uint256 _initialValue) {
32 | value = _initialValue;
33 | }
34 |
35 | // 读取数值 (view, 只读查询)
36 | function getValue() public view returns (uint256) {
37 | return value;
38 | }
39 |
40 | // 修改数值 (需要交易 & Gas)
41 | function setValue(uint256 _newValue) public {
42 | uint256 oldValue = value;
43 | value = _newValue;
44 | emit ValueUpdated(msg.sender, oldValue, _newValue);
45 | }
46 | }
47 |
48 | ```
49 | 这个测试合约包含了一个uint256形value变量,一个getValue函数获取value变量的值,一个setValue函数设置value变量的值。
50 | 我在optimism链的sepolia网络部署了该测试合约,合约地址为:0x833C27F4BFB4c1Eea93c747C3f5ECcf060c1B79d,有需要的话可以直接使用,由于一般的公用节点把链上事件监听方法给屏蔽了,因此这里需要使用私人节点,推荐使用[https://dashboard.alchemy.com/](https://dashboard.alchemy.com/) 获取免费的私人节点,节点获取方式如下图。
51 | 
52 | ## 示例
53 | ### 完整代码 EventListener.java
54 | [EventListener](../../java/EventListener.java)
55 |
56 | ```java
57 | import io.reactivex.Flowable;
58 | import org.web3j.abi.EventEncoder;
59 | import org.web3j.abi.FunctionReturnDecoder;
60 | import org.web3j.abi.TypeReference;
61 | import org.web3j.abi.datatypes.Address;
62 | import org.web3j.abi.datatypes.Event;
63 | import org.web3j.abi.datatypes.Type;
64 | import org.web3j.abi.datatypes.generated.Uint256;
65 | import org.web3j.protocol.Web3j;
66 | import org.web3j.protocol.core.DefaultBlockParameter;
67 | import org.web3j.protocol.core.DefaultBlockParameterName;
68 | import org.web3j.protocol.core.methods.request.EthFilter;
69 | import org.web3j.protocol.core.methods.response.Log;
70 | import org.web3j.protocol.http.HttpService;
71 |
72 | import java.math.BigInteger;
73 | import java.util.Arrays;
74 | import java.util.List;
75 |
76 | public class EventListener {
77 |
78 | private static final String RPC_URL = "RPC_URL";
79 | // Since most public rpc node has block the event api, so you should set your own rpc node url.
80 | // You can get a free usage rpc node from https://dashboard.alchemy.com/
81 |
82 | private static final String CONTRACT_ADDRESS = "CONTRACT_ADDRESS"; // Test contract address 0x833C27F4BFB4c1Eea93c747C3f5ECcf060c1B79d
83 |
84 | public static final Event VALUE_UPDATED = new Event(
85 | "ValueUpdated",
86 | Arrays.asList(
87 | new TypeReference(true) {}, // indexed `updater`
88 | new TypeReference() {}, // oldValue (non-indexed)
89 | new TypeReference() {} // newValue (non-indexed)
90 | )
91 | );
92 |
93 | private static final Web3j web3j = Web3j.build(new HttpService(RPC_URL));
94 |
95 | public void startLogListening() {
96 | DefaultBlockParameter startBlock = DefaultBlockParameterName.EARLIEST;
97 | // If you want to start from a specific block, you can set the block number as below
98 | // startBlock = DefaultBlockParameter.valueOf(blockNumber);
99 |
100 | EthFilter filter = new EthFilter(
101 | startBlock,
102 | DefaultBlockParameterName.LATEST, // You can change this value to set the latest block you want to listen to
103 | CONTRACT_ADDRESS
104 | );
105 |
106 | // Add event encoding to the filter
107 | filter.addOptionalTopics(
108 | EventEncoder.encode(VALUE_UPDATED)
109 | );
110 |
111 | // Listen to event logs
112 | Flowable logFlowable = web3j.ethLogFlowable(filter);
113 |
114 | logFlowable.subscribe(
115 | log -> {
116 | String eventSignature = log.getTopics().get(0);
117 | if (eventSignature.equals(EventEncoder.encode(VALUE_UPDATED))) {
118 | valueUpdated(log);
119 | }
120 | },
121 | throwable -> {
122 | System.err.println("Error processing event log: " + throwable.getMessage());
123 | }
124 | );
125 | }
126 |
127 | private void valueUpdated(Log log) {
128 | // Ensure log data is not empty
129 | if (log.getData() == null || log.getData().equals("0x")) {
130 | System.err.println("Log data is empty! Skipping this event.");
131 | return;
132 | }
133 |
134 | // Retrieve `updater` (indexed parameter stored in topics[1])
135 | String updater = "0x" + log.getTopics().get(1).substring(26); // Extract the last 40 characters of the address
136 |
137 | // Decode non-indexed parameters `oldValue` and `newValue`
138 | List decoded = FunctionReturnDecoder.decode(log.getData(), VALUE_UPDATED.getNonIndexedParameters());
139 |
140 | if (decoded.size() < 2) {
141 | System.err.println("Decoded data size is incorrect!");
142 | return;
143 | }
144 |
145 | int oldValue = ((BigInteger) decoded.get(0).getValue()).intValue();
146 | int newValue = ((BigInteger) decoded.get(1).getValue()).intValue();
147 |
148 | System.out.println("Value updated by " + updater + ", old: " + oldValue + ", new: " + newValue);
149 | }
150 |
151 | public static void main(String[] args) {
152 | EventListener eventListener = new EventListener();
153 | eventListener.startLogListening();
154 | }
155 | }
156 |
157 | ```
158 | 在上述代码中,我们首先要定义监听事件,这里需要特别注意,在合约代码中事件的定义`event ValueUpdated(address indexed updater, uint256 oldValue, uint256 newValue);`,其中updater是indexed,**所以需要给一个初始化true值**,其他的则不需要。
159 | ```java
160 | public static final Event VALUE_UPDATED = new Event(
161 | "ValueUpdated",
162 | Arrays.asList(
163 | new TypeReference(true) {}, // indexed `updater`
164 | new TypeReference() {}, // oldValue (non-indexed)
165 | new TypeReference() {} // newValue (non-indexed)
166 | )
167 | );
168 | ```
169 | 然后设置需要监听的事件过滤,这里需要配置你想要监听的区块范围,示例代码中设置的范围是从EARLIEST到LATEST,可以通过`DefaultBlockParameter.valueOf(blockNumber);`来设置区块号。
170 |
171 | ```java
172 | DefaultBlockParameter startBlock = DefaultBlockParameterName.EARLIEST;
173 | // If you want to start from a specific block, you can set the block number as below
174 | // startBlock = DefaultBlockParameter.valueOf(blockNumber);
175 |
176 | EthFilter filter = new EthFilter(
177 | startBlock,
178 | DefaultBlockParameterName.LATEST, // You can change this value to set the latest block you want to listen to
179 | CONTRACT_ADDRESS
180 | );
181 |
182 | // Add event encoding to the filter
183 | filter.addOptionalTopics(
184 | EventEncoder.encode(VALUE_UPDATED)
185 | );
186 | ```
187 | 开始进行监听,通过`log.getTopics().get(0)`来获取事件名称:
188 |
189 | ```java
190 | // Listen to event logs
191 | Flowable logFlowable = web3j.ethLogFlowable(filter);
192 |
193 | logFlowable.subscribe(
194 | log -> {
195 | String eventSignature = log.getTopics().get(0);
196 | if (eventSignature.equals(EventEncoder.encode(VALUE_UPDATED))) {
197 | valueUpdated(log);
198 | }
199 | },
200 | throwable -> {
201 | System.err.println("Error processing event log: " + throwable.getMessage());
202 | }
203 | );
204 | ```
205 | 解析事件:
206 |
207 | ```java
208 | private void valueUpdated(Log log) {
209 | // Ensure log data is not empty
210 | if (log.getData() == null || log.getData().equals("0x")) {
211 | System.err.println("Log data is empty! Skipping this event.");
212 | return;
213 | }
214 |
215 | // Retrieve `updater` (indexed parameter stored in topics[1])
216 | String updater = "0x" + log.getTopics().get(1).substring(26); // Extract the last 40 characters of the address
217 |
218 | // Decode non-indexed parameters `oldValue` and `newValue`
219 | List decoded = FunctionReturnDecoder.decode(log.getData(), VALUE_UPDATED.getNonIndexedParameters());
220 |
221 | if (decoded.size() < 2) {
222 | System.err.println("Decoded data size is incorrect!");
223 | return;
224 | }
225 |
226 | int oldValue = ((BigInteger) decoded.get(0).getValue()).intValue();
227 | int newValue = ((BigInteger) decoded.get(1).getValue()).intValue();
228 |
229 | System.out.println("Value updated by " + updater + ", old: " + oldValue + ", new: " + newValue);
230 | }
231 |
232 | ```
233 | 以上。
--------------------------------------------------------------------------------
/src/main/resources/Blog/Signature.md:
--------------------------------------------------------------------------------
1 | # Ethereum Message Signing and Verification in Java
2 | 🌍 **Languages:** [English](Signature.zh.md) | [简体中文](Signature.md)
3 |
4 | Web3j is a very useful tool, but it can be a bit complex to use. Therefore, I have written some usage examples that might help you.
5 |
6 | This chapter focuses on message signing and verification.
7 |
8 | ## Dependencies
9 |
10 | ### Maven Dependency
11 | ```xml
12 |
13 |
14 | org.web3j
15 | core
16 | 5.0.0
17 |
18 | ```
19 |
20 | ### Constants Class: CommonConstant.java
21 | ```java
22 | public class CommonConstant {
23 | public static final int PRIVATE_KEY_RADIX = 16;
24 | public static final int SIGNATURE_BYTE_LENGTH = 65;
25 | public static final int V_INDEX = 64;
26 | public static final int V_BASE = 27;
27 | public static final int V_LOWER_BOUND = 27;
28 | public static final int R_START_INDEX = 0;
29 | public static final int R_END_INDEX = 32;
30 | public static final int S_START_INDEX = 32;
31 | public static final int S_END_INDEX = 64;
32 | public static final String ADDRESS_PREFIX = "0x";
33 | }
34 | ```
35 |
36 | ## Example
37 |
38 | ### Signing a Message
39 | ```java
40 | /**
41 | * Signs a message using a given private key and returns the signature in hexadecimal format.
42 | * This function applies the Ethereum-specific prefix to the message before signing.
43 | *
44 | * @param privateKeyHex The private key in hexadecimal format.
45 | * @param message The message to be signed.
46 | * @return The generated signature as a hexadecimal string.
47 | */
48 | public static String signPrefixedMessage(String privateKeyHex, String message) {
49 | BigInteger privateKey = new BigInteger(privateKeyHex, CommonConstant.PRIVATE_KEY_RADIX);
50 |
51 | ECKeyPair keyPair = ECKeyPair.create(privateKey);
52 | Sign.SignatureData signatureData = Sign.signPrefixedMessage(message.getBytes(), keyPair);
53 |
54 | return Numeric.toHexStringNoPrefix(signatureData.getR()) +
55 | Numeric.toHexStringNoPrefix(signatureData.getS()) +
56 | Numeric.toHexStringNoPrefix(signatureData.getV());
57 | }
58 | ```
59 |
60 | ### Validating a Signature
61 | ```java
62 | /**
63 | * Validates a given signature against the specified message and wallet address.
64 | *
65 | * @param signature The digital signature to validate, in hexadecimal format.
66 | * @param message The message (original message) that was signed.
67 | * @param walletAddress The wallet address expected to match the signature.
68 | * @return True if the signature is valid and corresponds to the wallet
69 | * address; false otherwise.
70 | */
71 | public static Boolean isSignatureValid(String signature, String message, String walletAddress) {
72 | if (StringUtils.isAnyBlank(signature, message, walletAddress)) {
73 | return false;
74 | }
75 |
76 | byte[] signatureBytes = Numeric.hexStringToByteArray(signature);
77 | if (signatureBytes.length != CommonConstant.SIGNATURE_BYTE_LENGTH) {
78 | return false;
79 | }
80 |
81 | byte v = signatureBytes[CommonConstant.V_INDEX];
82 | if (v < CommonConstant.V_LOWER_BOUND) {
83 | v += CommonConstant.V_BASE;
84 | }
85 | Sign.SignatureData signatureData = new Sign.SignatureData(
86 | v,
87 | Arrays.copyOfRange(signatureBytes, CommonConstant.R_START_INDEX, CommonConstant.R_END_INDEX),
88 | Arrays.copyOfRange(signatureBytes, CommonConstant.S_START_INDEX, CommonConstant.S_END_INDEX)
89 | );
90 | BigInteger publicKey;
91 | try {
92 | publicKey = Sign.signedPrefixedMessageToKey(message.getBytes(), signatureData);
93 | } catch (SignatureException e) {
94 | return false;
95 | }
96 | String parsedAddress = CommonConstant.ADDRESS_PREFIX + Keys.getAddress(publicKey);
97 | return parsedAddress.equalsIgnoreCase(walletAddress);
98 | }
99 | ```
100 |
101 | ## Sample Code
102 | [Signature](../../java/Signature.java)
--------------------------------------------------------------------------------
/src/main/resources/Blog/Signature.zh.md:
--------------------------------------------------------------------------------
1 | # Java中如何实现以太坊消息签名及验证
2 | 🌍 **Languages:** [English](Signature.md) | [简体中文](Signature.zh.md)
3 |
4 | Web3j是一个很好用的工具,但是使用起来有点复杂,因此我写了一些使用示例,希望可以帮到各位。
5 | 完整示例代码仓库地址:[web3j-eth-sample](https://github.com/zhoujingweb3/web3j-eth-sample)
6 | 本章主要是消息签名及验证。
7 | ## 依赖
8 | ### Maven依赖
9 | ```java
10 |
11 |
12 | org.web3j
13 | core
14 | 5.0.0
15 |
16 |
17 | ```
18 | ### 常量类 CommonConstant.java
19 |
20 | ```java
21 | public class CommonConstant {
22 | public static final int PRIVATE_KEY_RADIX = 16;
23 | public static final int SIGNATURE_BYTE_LENGTH = 65;
24 | public static final int V_INDEX = 64;
25 | public static final int V_BASE = 27;
26 | public static final int V_LOWER_BOUND = 27;
27 | public static final int R_START_INDEX = 0;
28 | public static final int R_END_INDEX = 32;
29 | public static final int S_START_INDEX = 32;
30 | public static final int S_END_INDEX = 64;
31 | public static final String ADDRESS_PREFIX = "0x";
32 | }
33 |
34 | ```
35 |
36 | ## 示例
37 | ### 给消息签名
38 |
39 | ```java
40 | /**
41 | * Signs a message using a given private key and returns the signature in hexadecimal format.
42 | * This function applies the Ethereum-specific prefix to the message before signing.
43 | *
44 | * @param privateKeyHex The private key in hexadecimal format.
45 | * @param message The message to be signed.
46 | * @return The generated signature as a hexadecimal string.
47 | */
48 | public static String signPrefixedMessage(String privateKeyHex, String message) {
49 | BigInteger privateKey = new BigInteger(privateKeyHex, CommonConstant.PRIVATE_KEY_RADIX);
50 |
51 | ECKeyPair keyPair = ECKeyPair.create(privateKey);
52 | Sign.SignatureData signatureData = Sign.signPrefixedMessage(message.getBytes(), keyPair);
53 |
54 | return Numeric.toHexStringNoPrefix(signatureData.getR()) +
55 | Numeric.toHexStringNoPrefix(signatureData.getS()) +
56 | Numeric.toHexStringNoPrefix(signatureData.getV());
57 | }
58 | ```
59 |
60 | ### 验证签名是否有效
61 |
62 | ```java
63 | /**
64 | * Validates a given signature against the specified message and wallet address.
65 | *
66 | * @param signature The digital signature to validate, in hexadecimal format.
67 | * @param message The message (original message) that was signed.
68 | * @param walletAddress The wallet address expected to match the signature.
69 | * @return True if the signature is valid and corresponds to the wallet
70 | * address; false otherwise.
71 | */
72 | public static Boolean isSignatureValid(String signature, String message, String walletAddress) {
73 | if (StringUtils.isAnyBlank(signature, message, walletAddress)) {
74 | return false;
75 | }
76 |
77 | byte[] signatureBytes = Numeric.hexStringToByteArray(signature);
78 | if (signatureBytes.length != CommonConstant.SIGNATURE_BYTE_LENGTH) {
79 | return false;
80 | }
81 |
82 | byte v = signatureBytes[CommonConstant.V_INDEX];
83 | if (v < CommonConstant.V_LOWER_BOUND) {
84 | v += CommonConstant.V_BASE;
85 | }
86 | Sign.SignatureData signatureData = new Sign.SignatureData(
87 | v,
88 | Arrays.copyOfRange(signatureBytes, CommonConstant.R_START_INDEX, CommonConstant.R_END_INDEX),
89 | Arrays.copyOfRange(signatureBytes, CommonConstant.S_START_INDEX, CommonConstant.S_END_INDEX)
90 | );
91 | BigInteger publicKey;
92 | try {
93 | publicKey = Sign.signedPrefixedMessageToKey(message.getBytes(), signatureData);
94 | } catch (SignatureException e) {
95 | return false;
96 | }
97 | String parsedAddress = CommonConstant.ADDRESS_PREFIX + Keys.getAddress(publicKey);
98 | return parsedAddress.equalsIgnoreCase(walletAddress);
99 | }
100 | ```
101 |
102 | ## 示例代码
103 | [Signature](../../java/Signature.java)
--------------------------------------------------------------------------------
/src/main/resources/Blog/Transfer.md:
--------------------------------------------------------------------------------
1 | # How to Transfer ETH in Java
2 |
3 | 🌍 **Languages:** [English](Transfer.md) | [简体中文](Transfer.zh.md)
4 |
5 | Web3j is a powerful tool, but it can be somewhat complex to use. Therefore, I have provided some usage examples to help you get started.
6 |
7 | Complete example code repository: [web3j-eth-sample](https://github.com/zhoujingweb3/web3j-eth-sample)
8 |
9 | This article covers how to use Web3j in Java to check Ethereum balances and transfer ETH.
10 |
11 | ## Dependencies
12 | ### Maven Dependency
13 | ```xml
14 |
15 |
16 | org.web3j
17 | core
18 | 5.0.0
19 |
20 | ```
21 |
22 | ## Examples
23 | ### Checking Balance
24 |
25 | ```java
26 | /**
27 | * Retrieves the Ether balance of the given Ethereum address.
28 | *
29 | * @param address The Ethereum address whose balance is to be retrieved.
30 | * @return The balance in Ether as a BigDecimal.
31 | * @throws IOException If there is an issue communicating with the Ethereum node.
32 | */
33 | public static BigDecimal getETHBalance(String address) throws IOException {
34 | // Retrieve balance in Wei
35 | BigInteger balanceInWei = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send().getBalance();
36 | // Convert Wei to Ether
37 | return Convert.fromWei(new BigDecimal(balanceInWei), Convert.Unit.ETHER);
38 | }
39 | ```
40 |
41 | ### Transfer ETH
42 | ```java
43 | /**
44 | * Transfers Ether from a sender's account to a recipient's account.
45 | *
46 | * @param senderPrivateKey The private key of the sender's Ethereum account (keep this secure).
47 | * @param recipientAddress The recipient's Ethereum address.
48 | * @param amountInEther The amount to transfer in Ether.
49 | * @return The transaction hash if the transfer is successful; otherwise, returns null.
50 | * @throws IOException If there is an issue communicating with the Ethereum node.
51 | */
52 | public static String transfer(String senderPrivateKey, String recipientAddress, BigDecimal amountInEther) throws IOException {
53 | // Retrieve the chain ID of the connected network
54 | EthChainId chainIdResponse = web3j.ethChainId().send();
55 | long chainId = chainIdResponse.getChainId().longValue();
56 |
57 | /* Sender Account Configuration */
58 | // Create credentials from the sender's private key
59 | Credentials credentials = Credentials.create(senderPrivateKey);
60 | String senderAddress = credentials.getAddress();
61 | // Get the current nonce for the sender's account
62 | EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
63 | senderAddress, DefaultBlockParameterName.LATEST).send();
64 | BigInteger nonce = ethGetTransactionCount.getTransactionCount();
65 |
66 | /* Transaction Amount and Fee Configuration */
67 | // Convert the amount from Ether to Wei (1 ETH = 10^18 Wei)
68 | BigInteger value = Convert.toWei(amountInEther, Convert.Unit.ETHER).toBigInteger();
69 | // Get the current Gas price
70 | EthGasPrice ethGasPrice = web3j.ethGasPrice().send();
71 | BigInteger gasPrice = ethGasPrice.getGasPrice();
72 | // Gas limit for a standard Ether transfer (typically 21,000)
73 | BigInteger gasLimit = BigInteger.valueOf(21000);
74 |
75 | /* Transfer */
76 | // Create the transaction object
77 | RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
78 | nonce, gasPrice, gasLimit, recipientAddress, value);
79 | // Sign the transaction
80 | byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);
81 | String hexValue = Numeric.toHexString(signedMessage);
82 | // Send the transaction
83 | EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
84 | // Return the transaction hash if successful, or null if the transaction failed
85 | return ethSendTransaction.getTransactionHash();
86 | }
87 | ```
88 |
89 | Since this code is a little bit complex, let’s analyze it step by step.
90 |
91 | ### Retrieving Network Node Information
92 | ```java
93 | // Retrieve the chain ID of the connected network
94 | EthChainId chainIdResponse = web3j.ethChainId().send();
95 | long chainId = chainIdResponse.getChainId().longValue();
96 | ```
97 | According to the EIP-155 standard, to prevent replay attacks, we need to include the `chainId` as a parameter when signing transactions. Therefore, we must retrieve the `chainId` of the RPC node.
98 |
99 | In practice, this operation is usually performed once and then reused.
100 |
101 | ### Configuring the Sender’s Account
102 | ```java
103 | /* Sender Account Configuration */
104 | // Create credentials from the sender's private key
105 | Credentials credentials = Credentials.create(senderPrivateKey);
106 | String senderAddress = credentials.getAddress();
107 | // Get the current nonce for the sender's account
108 | EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
109 | senderAddress, DefaultBlockParameterName.LATEST).send();
110 | BigInteger nonce = ethGetTransactionCount.getTransactionCount();
111 | ```
112 |
113 | ### Setting Transaction Gas
114 | ```java
115 | /* Transaction Amount and Fee Configuration */
116 | // Convert the amount from Ether to Wei (1 ETH = 10^18 Wei)
117 | BigInteger value = Convert.toWei(amountInEther, Convert.Unit.ETHER).toBigInteger();
118 | // Get the current Gas price
119 | EthGasPrice ethGasPrice = web3j.ethGasPrice().send();
120 | BigInteger gasPrice = ethGasPrice.getGasPrice();
121 | // Gas limit for a standard Ether transfer (typically 21,000)
122 | BigInteger gasLimit = BigInteger.valueOf(21000);
123 | ```
124 | We typically set `gasPrice` to the current real-time gas consumption value. In real-world applications, you may adjust `gasPrice` dynamically.
125 |
126 | For a standard Ether transfer, the gas consumption limit is usually `21000`.
127 |
128 | ### Executing the Transfer
129 | ```java
130 | /* Transfer */
131 | // Create the transaction object
132 | RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
133 | nonce, gasPrice, gasLimit, recipientAddress, value);
134 | // Sign the transaction
135 | byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);
136 | String hexValue = Numeric.toHexString(signedMessage);
137 | // Send the transaction
138 | EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
139 | // Return the transaction hash if successful, or null if the transaction failed
140 | return ethSendTransaction.getTransactionHash();
141 | ```
142 |
143 | ### Complete Code: `Transfer.java`
144 | [Transfer.java](../../java/Transfer.java)
145 |
146 | In the `main` function, we first check the recipient’s balance, then transfer `0.001 ETH`, wait for `15 seconds`, and check the balance again. Under normal conditions, the recipient’s address should increase by `0.001 ETH`.
147 |
148 |
--------------------------------------------------------------------------------
/src/main/resources/Blog/Transfer.zh.md:
--------------------------------------------------------------------------------
1 | # Java中如何转账发送ETH
2 | 🌍 **Languages:** [English](Transfer.md) | [简体中文](Transfer.zh.md)
3 |
4 | Web3j是一个很好用的工具,但是使用起来有点复杂,因此我写了一些使用示例,希望可以帮到各位。
5 | 完整示例代码仓库地址:[web3j-eth-sample](https://github.com/zhoujingweb3/web3j-eth-sample)
6 | 本章内容是如何使用Web3j在Java是进行以太坊余额查询和转账,发送ETH。
7 | ## 依赖
8 | ### Maven依赖
9 | ```java
10 |
11 |
12 | org.web3j
13 | core
14 | 5.0.0
15 |
16 |
17 | ```
18 | ## 示例
19 | ### 查询余额
20 |
21 | ```java
22 | /**
23 | * Retrieves the Ether balance of the given Ethereum address.
24 | *
25 | * @param address The Ethereum address whose balance is to be retrieved.
26 | * @return The balance in Ether as a BigDecimal.
27 | * @throws IOException If there is an issue communicating with the Ethereum node.
28 | */
29 | public static BigDecimal getETHBalance(String address) throws IOException {
30 | // Retrieve balance in Wei
31 | BigInteger balanceInWei = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send().getBalance();
32 | // Convert Wei to Ether
33 | return Convert.fromWei(new BigDecimal(balanceInWei), Convert.Unit.ETHER);
34 | }
35 | ```
36 |
37 | ### 转账,发送ETH
38 | ```java
39 | /**
40 | * Transfers Ether from a sender's account to a recipient's account.
41 | *
42 | * @param senderPrivateKey The private key of the sender's Ethereum account (keep this secure).
43 | * @param recipientAddress The recipient's Ethereum address.
44 | * @param amountInEther The amount to transfer in Ether.
45 | * @return The transaction hash if the transfer is successful; otherwise, returns null.
46 | * @throws IOException If there is an issue communicating with the Ethereum node.
47 | */
48 | public static String transfer(String senderPrivateKey, String recipientAddress, BigDecimal amountInEther) throws IOException {
49 | // Retrieve the chain ID of the connected network
50 | EthChainId chainIdResponse = web3j.ethChainId().send();
51 | long chainId = chainIdResponse.getChainId().longValue();
52 |
53 | /* Sender Account Configuration */
54 | // Create credentials from the sender's private key
55 | Credentials credentials = Credentials.create(senderPrivateKey);
56 | String senderAddress = credentials.getAddress();
57 | // Get the current nonce for the sender's account
58 | EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
59 | senderAddress, DefaultBlockParameterName.LATEST).send();
60 | BigInteger nonce = ethGetTransactionCount.getTransactionCount();
61 |
62 | /* Transaction Amount and Fee Configuration */
63 | // Convert the amount from Ether to Wei (1 ETH = 10^18 Wei)
64 | BigInteger value = Convert.toWei(amountInEther, Convert.Unit.ETHER).toBigInteger();
65 | // Get the current Gas price
66 | EthGasPrice ethGasPrice = web3j.ethGasPrice().send();
67 | BigInteger gasPrice = ethGasPrice.getGasPrice();
68 | // Gas limit for a standard Ether transfer (typically 21,000)
69 | BigInteger gasLimit = BigInteger.valueOf(21000);
70 |
71 | /* Transfer */
72 | // Create the transaction object
73 | RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
74 | nonce, gasPrice, gasLimit, recipientAddress, value);
75 | // Sign the transaction
76 | byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);
77 | String hexValue = Numeric.toHexString(signedMessage);
78 | // Send the transaction
79 | EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
80 | // Return the transaction hash if successful, or null if the transaction failed
81 | return ethSendTransaction.getTransactionHash();
82 | }
83 | ```
84 | 由于这段代码较长,现在我们逐段分析。
85 | #### 获取网络节点信息
86 |
87 | ```java
88 | // Retrieve the chain ID of the connected network
89 | EthChainId chainIdResponse = web3j.ethChainId().send();
90 | long chainId = chainIdResponse.getChainId().longValue();
91 | ```
92 | 基于EIP155规范,为了防御重放攻击,我们需要将chainId作为交易签名的参数之一,因此,我们还需要获取RPC节点对应的chainId。
93 | 工程实践中,我们通常只需要完成一次上述操作,然后复用chainId。
94 | #### 设置转账账户信息
95 | ```java
96 | /* Sender Account Configuration */
97 | // Create credentials from the sender's private key
98 | Credentials credentials = Credentials.create(senderPrivateKey);
99 | String senderAddress = credentials.getAddress();
100 | // Get the current nonce for the sender's account
101 | EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
102 | senderAddress, DefaultBlockParameterName.LATEST).send();
103 | BigInteger nonce = ethGetTransactionCount.getTransactionCount();
104 | ```
105 | #### 设置交易gas
106 | ```java
107 | /* Transaction Amount and Fee Configuration */
108 | // Convert the amount from Ether to Wei (1 ETH = 10^18 Wei)
109 | BigInteger value = Convert.toWei(amountInEther, Convert.Unit.ETHER).toBigInteger();
110 | // Get the current Gas price
111 | EthGasPrice ethGasPrice = web3j.ethGasPrice().send();
112 | BigInteger gasPrice = ethGasPrice.getGasPrice();
113 | // Gas limit for a standard Ether transfer (typically 21,000)
114 | BigInteger gasLimit = BigInteger.valueOf(21000);
115 | ```
116 | 通常我们设置 gasPrice 为当前实时gas消耗值,考虑到实际应用,你可以动态的增加或减少 gasPrice。
117 | 在一次转账中,通常gas消耗上限值为21000。
118 | #### 执行转账,发送ETH至目标地址
119 | ```java
120 | /* Transfer */
121 | // Create the transaction object
122 | RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
123 | nonce, gasPrice, gasLimit, recipientAddress, value);
124 | // Sign the transaction
125 | byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);
126 | String hexValue = Numeric.toHexString(signedMessage);
127 | // Send the transaction
128 | EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
129 | // Return the transaction hash if successful, or null if the transaction failed
130 | return ethSendTransaction.getTransactionHash();
131 | ```
132 |
133 | ## 示例代码
134 | [Transfer](../../java/Transfer.java)
135 |
136 | 在示例代码的,Main函数中,我们首先查询了收款人地址余额,然后向收款人地址转账0.001ETH,等待15秒钟后,再次查询收款人地址余额,正常情况下,我们会发现收款人地址增加了0.001ETH。
--------------------------------------------------------------------------------
/src/main/resources/Blog/Wallet.md:
--------------------------------------------------------------------------------
1 | # Ethereum Wallet Operations in Java
2 | 🌍 **Languages:** [English](Wallet.md) | [简体中文](Wallet.zh.md)
3 |
4 | Web3j is a very useful tool, but it can be a bit complex to use. Therefore, I have written some usage examples that might help you.
5 | This chapter focuses on wallet-related operations.
6 |
7 | ## Dependencies
8 | ### Maven Dependency
9 | ```xml
10 |
11 |
12 | org.web3j
13 | core
14 | 5.0.0
15 |
16 | ```
17 | ### Constants Class `CommonConstant.java`
18 | ```java
19 | public class CommonConstant {
20 | public static final int PRIVATE_KEY_RADIX = 16;
21 | public static final int SIGNATURE_BYTE_LENGTH = 65;
22 | public static final int V_INDEX = 64;
23 | public static final int V_BASE = 27;
24 | public static final int V_LOWER_BOUND = 27;
25 | public static final int R_START_INDEX = 0;
26 | public static final int R_END_INDEX = 32;
27 | public static final int S_START_INDEX = 32;
28 | public static final int S_END_INDEX = 64;
29 | public static final String ADDRESS_PREFIX = "0x";
30 | }
31 | ```
32 | ## Examples
33 | ### Generate a Random Wallet
34 | ```java
35 | /**
36 | * Generates a random Ethereum private key.
37 | *
38 | * @return A randomly generated private key in hexadecimal format.
39 | * @throws InvalidAlgorithmParameterException If the cryptographic algorithm parameters are invalid.
40 | * @throws NoSuchAlgorithmException If the cryptographic algorithm is not available.
41 | * @throws NoSuchProviderException If the security provider is not available.
42 | */
43 | public static String createRandomPrivateKey() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {
44 | // Generate a random ECKeyPair (contains both private and public keys)
45 | ECKeyPair ecKeyPair = Keys.createEcKeyPair();
46 | // Return the private key as a hexadecimal string
47 | return ecKeyPair.getPrivateKey().toString(CommonConstant.PRIVATE_KEY_RADIX);
48 | }
49 | ```
50 | ### Get Wallet Address from a Private Key
51 | ```java
52 | /**
53 | * Generates an Ethereum wallet address from a given private key.
54 | *
55 | * @param privateKeyHex The private key in hexadecimal format.
56 | * @return The generated Ethereum wallet address (starting with "0x").
57 | */
58 | public static String getWalletAddressFromPrivateKeyHex(String privateKeyHex) {
59 | // Convert the private key from hexadecimal format to BigInteger
60 | BigInteger privateKey = new BigInteger(privateKeyHex, CommonConstant.PRIVATE_KEY_RADIX);
61 | // Create an ECKeyPair object (contains both private and public keys)
62 | ECKeyPair keyPair = ECKeyPair.create(privateKey);
63 | // Generate a wallet address from the public key and return it with "0x" prefix
64 | return CommonConstant.ADDRESS_PREFIX + Keys.getAddress(keyPair.getPublicKey());
65 | }
66 | ```
67 | ### Validate if a Wallet Address is Correct
68 | ```java
69 | /**
70 | * Validates whether a given Ethereum wallet address is in a correct format.
71 | *
72 | * @param address The Ethereum wallet address (should start with "0x").
73 | * @return True if the address is valid, false otherwise.
74 | */
75 | public static boolean isValidAddress(String address) {
76 | return WalletUtils.isValidAddress(address);
77 | }
78 | ```
79 | ### Validate if a Private Key is Correct
80 | ```java
81 | /**
82 | * Validates whether a given private key is valid.
83 | *
84 | * @param privateKey The private key in hexadecimal format.
85 | * @return True if the private key is valid, false otherwise.
86 | */
87 | public static boolean isValidPrivateKey(String privateKey) {
88 | return WalletUtils.isValidPrivateKey(privateKey);
89 | }
90 | ```
91 | ## Sample Code
92 | [Wallet](../../java/Wallet.java)
--------------------------------------------------------------------------------
/src/main/resources/Blog/Wallet.zh.md:
--------------------------------------------------------------------------------
1 | # Java中操作以太坊钱包
2 | 🌍 **Languages:** [English](Wallet.md) | [简体中文](Wallet.zh.md)
3 |
4 | Web3j是一个很好用的工具,但是使用起来有点复杂,因此我写了一些使用示例,希望可以帮到各位。
5 | 本章主要钱包的相关操作。
6 | ## 依赖
7 | ### Maven依赖
8 | ```
9 |
10 |
11 | org.web3j
12 | core
13 | 5.0.0
14 |
15 | ```
16 | ### 常量类 CommonConstant.java
17 |
18 | ```java
19 | public class CommonConstant {
20 | public static final int PRIVATE_KEY_RADIX = 16;
21 | public static final int SIGNATURE_BYTE_LENGTH = 65;
22 | public static final int V_INDEX = 64;
23 | public static final int V_BASE = 27;
24 | public static final int V_LOWER_BOUND = 27;
25 | public static final int R_START_INDEX = 0;
26 | public static final int R_END_INDEX = 32;
27 | public static final int S_START_INDEX = 32;
28 | public static final int S_END_INDEX = 64;
29 | public static final String ADDRESS_PREFIX = "0x";
30 | }
31 |
32 | ```
33 |
34 | ## 示例
35 |
36 | ### 生成随机钱包
37 |
38 | ```java
39 | /**
40 | * Generates a random Ethereum private key.
41 | *
42 | * @return A randomly generated private key in hexadecimal format.
43 | * @throws InvalidAlgorithmParameterException If the cryptographic algorithm parameters are invalid.
44 | * @throws NoSuchAlgorithmException If the cryptographic algorithm is not available.
45 | * @throws NoSuchProviderException If the security provider is not available.
46 | */
47 | public static String createRandomPrivateKey() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {
48 | // Generate a random ECKeyPair (contains both private and public keys)
49 | ECKeyPair ecKeyPair = Keys.createEcKeyPair();
50 | // Return the private key as a hexadecimal string
51 | return ecKeyPair.getPrivateKey().toString(CommonConstant.PRIVATE_KEY_RADIX);
52 | }
53 | ```
54 |
55 | ### 根据私钥获取钱包地址
56 |
57 | ```java
58 | /**
59 | * Generates an Ethereum wallet address from a given private key.
60 | *
61 | * @param privateKeyHex The private key in hexadecimal format.
62 | * @return The generated Ethereum wallet address (starting with "0x").
63 | */
64 | public static String getWalletAddressFromPrivateKeyHex(String privateKeyHex) {
65 | // Convert the private key from hexadecimal format to BigInteger
66 | BigInteger privateKey = new BigInteger(privateKeyHex, CommonConstant.PRIVATE_KEY_RADIX);
67 | // Create an ECKeyPair object (contains both private and public keys)
68 | ECKeyPair keyPair = ECKeyPair.create(privateKey);
69 | // Generate a wallet address from the public key and return it with "0x" prefix
70 | return CommonConstant.ADDRESS_PREFIX + Keys.getAddress(keyPair.getPublicKey());
71 | }
72 | ```
73 | ### 判断钱包地址是否合法
74 |
75 | ```java
76 | /**
77 | * Validates whether a given Ethereum wallet address is in a correct format.
78 | *
79 | * @param address The Ethereum wallet address (should start with "0x").
80 | * @return True if the address is valid, false otherwise.
81 | */
82 | public static boolean isValidAddress(String address) {
83 | return WalletUtils.isValidAddress(address);
84 | }
85 | ```
86 |
87 | ### 判断私钥是否合法
88 |
89 | ```java
90 | /**
91 | * Validates whether a given private key is valid.
92 | *
93 | * @param privateKey The private key in hexadecimal format.
94 | * @return True if the private key is valid, false otherwise.
95 | */
96 | public static boolean isValidPrivateKey(String privateKey) {
97 | return WalletUtils.isValidPrivateKey(privateKey);
98 | }
99 | ```
100 | ## 示例代码
101 | [Wallet](../../java/Wallet.java)
--------------------------------------------------------------------------------