以太坊作为全球第二大区块链平台,其原生代币ETH以及基于ERC标准的代币(如USDT、USDC等)的普及,使得个人钱包的管理变得日益重要,虽然市面上已有众多成熟的以太坊钱包应用,但对于开发者而言,使用Java语言从零开始搭建一个属于自己的以太坊钱包,不仅能深入理解区块链技术的底层原理,还能为开发更复杂的去中心化应用(DApp)或金融服务打下坚实基础,本文将详细介绍如何使用Java语言搭建一个基础的以太坊钱包。

准备工作:开发环境与依赖

在开始之前,我们需要准备以下环境和工具:

  1. Java开发环境:确保你的系统已安装JDK 8或更高版本,并配置好JAVA_HOME环境变量。
  2. 构建工具:Maven或Gradle,本文以Maven为例。
  3. 以太坊客户端库:我们将使用成熟的Java以太坊库web3j,它提供了与以太坊节点交互的丰富API,包括创建钱包、查询余额、发送交易等。
  4. 以太坊节点:可以连接到公共的以太坊节点(如Infura、Alchemy),或者运行本地节点(如Geth或Parity),对于开发测试,公共节点更为便捷。

创建Maven项目并引入依赖

创建一个Maven项目,并在pom.xml文件中添加web3j的核心依赖:

<dependencies>
    <!-- Web3j核心库 -->
    <dependency>
        <groupId>org.web3j</groupId>
        <artifactId>core</artifactId>
        <version>4.9.8</version> <!-- 请使用最新版本 -->
    </dependency>
    <!-- 用于生成 solidity 相关代码的依赖 (可选) -->
    <dependency>
        <groupId>org.web3j</groupId>
        <artifactId>codegen</artifactId>
        <version>4.9.8</version>
    </dependency>
    <!-- 日志依赖 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
    </dependency>
</dependencies>

生成以太坊钱包(创建账户)

以太坊钱包的核心是账户,每个账户由一对公钥和私钥组成,其中私钥用于签名交易,公钥和地址则用于接收资金。web3j提供了方便的工具来生成和管理这些密钥。

创建一个新的Java类WalletGenerator,编写以下代码来生成一个新钱包:

import org.web3j.crypto.CipherException;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Wallet;
import org.web3j.crypto.WalletFile;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
public class WalletGenerator {
    public static void main(String[] args) {
        try {
            // 1. 生成随机密钥对
            ECKeyPair ecKeyPair = Keys.createEcKeyPair();
            // 2. 根据密钥对创建钱包文件 (默认使用Scrypt加密算法)
            WalletFile walletFile = Wallet.createStandard("your-password", ecKeyPair); // "your-password"是你为钱包设置的密码
            // 3. 将钱包文件保存到本地
            String walletDir = "wallets"; // 钱包存储目录
            File walletFileObj = new File(walletDir);
            if (!walletFileObj.exists()) {
                walletFileObj.mkdirs();
            }
            String fileName = walletFile.getAddress() + ".json";
            WalletUtils.saveWalletFile(new File(walletDir, fileName), walletFile);
            System.out.println("钱包创建成功!");
            System.out.println("钱包地址: " + walletFile.getAddress());
            System.out.println("私钥 (请妥善保管,切勿泄露): " + org.web3j.crypto.WalletUtils.privateKeyFromKeyPair(ecKeyPair).toString(16));
            System.out.println("钱包文件已保存至: " + walletDir + File.separator + fileName);
        } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchProviderException | CipherException | IOException e) {
            e.printStackTrace();
        }
    }
}

说明

  • Keys.createEcKeyPair():生成随机的椭圆曲线密钥对。
  • Wallet.createStandard(password, ecKeyPair):使用标准算法(Scrypt)创建钱包文件,需要设置一个密码。
  • WalletUtils.saveWalletFile():将钱包文件保存到指定目录,钱包文件是一个JSON文件,包含地址、加密的私钥等信息。
  • 重要:私钥是控制钱包资产的关键,生成后必须极其小心地保存,绝对不要泄露给他人,在实际应用中,私钥的存储需要极高的安全性。

运行上述代码,你将在wallets目录下看到一个以钱包地址命名的.json文件,这就是你的以太坊钱包文件。

加载钱包与查询余额

生成钱包后,我们需要能够加载它并进行操作,比如查询账户余额。

创建WalletManager类:

import org.web3j.crypto.CipherException;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.EthGetBalance;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Convert;
import org.web3j.utils.Convert.Unit;
import org.web3j.utils.Numeric;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
public class WalletManager {
    // 替换为你的Infura或Alchemy节点URL
    private static final String INFURA_URL = "https://mainnet.infura.io/v3/YOUR_PROJECT_ID&q
随机配图
uot;; private static final String WALLET_DIR = "wallets"; private static final String WALLET_PASSWORD = "your-password"; public static void main(String[] args) { // 1. 连接到以太坊节点 Web3j web3j = Web3j.build(new HttpService(INFURA_URL)); try { // 2. 加载钱包 (假设我们已经有了一个钱包文件) String walletFileName = "YOUR_WALLET_ADDRESS.json"; // 替换为你的钱包文件名 File walletFile = new File(WALLET_DIR, walletFileName); // 3. 解析钱包文件,获取Credentials对象 org.web3j.crypto.Credentials credentials = WalletUtils.loadCredentials(WALLET_PASSWORD, walletFile); String walletAddress = credentials.getAddress(); System.out.println("钱包地址: " + walletAddress); // 4. 查询账户余额 EthGetBalance balance = web3j.ethGetBalance(walletAddress, org.web3j.protocol.core.DefaultBlockParameterName.LATEST).send(); BigInteger balanceWei = balance.getBalance(); BigDecimal balanceEth = Convert.fromWei(new BigDecimal(balanceWei), Unit.ETHER); System.out.println("账户余额: " + balanceEth + " ETH"); // 5. (可选) 查询nonce (交易次数) EthGetTransactionCount nonce = web3j.ethGetTransactionCount(walletAddress, org.web3j.protocol.core.DefaultBlockParameterName.LATEST).send(); BigInteger transactionCount = nonce.getTransactionCount(); System.out.println("Nonce: " + transactionCount); // 6. (可选) 发送ETH (这里仅作示例,实际发送需要足够的ETH支付gas费) /* String toAddress = "0xRecipientAddressHere"; BigInteger value = Convert.toWei("0.01", Unit.ETHER).toBigInteger(); BigInteger gasLimit = BigInteger.valueOf(21000); // 转账ETH通常21000 gas BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice(); // 获取当前建议gas价格 EthSendTransaction transaction = web3j.ethSendTransaction( org.web3j.protocol.core.methods.Transaction.createEtherTransaction( walletAddress, nonce.getTransactionCount(), gasPrice, gasLimit, toAddress, value)) .send(); if (transaction.getTransactionHash() != null) { System.out.println("交易发送成功,哈希: " + transaction.getTransactionHash()); // 等待交易被打包 TransactionReceipt receipt = web3j.ethGetTransactionReceipt(transaction.getTransactionHash()).send().getTransactionReceipt().orElse(null); if (receipt != null) { System.out.println("交易状态: " + receipt.getStatus()); } } else { System.out.println("交易发送失败: " + transaction.getError().getMessage()); } */