├── .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 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 22 | 23 | 24 | 26 | 27 | 29 | { 30 | "associatedIndex": 4 31 | } 32 | 33 | 34 | 35 | 36 | 37 | 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 | 65 | 66 | 72 | 73 | 79 | 80 | 86 | 87 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 1738374357910 107 | 111 | 112 | 119 | 120 | 127 | 128 | 135 | 136 | 143 | 144 | 151 | 152 | 159 | 160 | 167 | 168 | 175 | 176 | 183 | 184 | 191 | 192 | 199 | 200 | 207 | 208 | 215 | 216 | 223 | 224 | 231 | 232 | 239 | 240 | 247 | 248 | 255 | 256 | 263 | 264 | 271 | 272 | 279 | 280 | 287 | 288 | 295 | 298 | 299 | 308 | 309 | 310 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 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 | ![私人RPC节点URL](https://i-blog.csdnimg.cn/direct/15271f8f68f540e8b85374e4f19de6ad.png) 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) --------------------------------------------------------------------------------