使用 Java & Web3j 部署 ERC20 智能合约并调用功能(实战手册)

·

想要让 Java 项目直接与 以太坊网络 交互,部署并操作 ERC20 通证
本文用一份零删减、可跑的 最小可复现代码,带你把 Solidity 到 Spring Boot 的全部流程打通。

为什么要用 Java 直调智能合约

下文围绕五个关键词 “Java”“Web3j”“部署”“ERC20”“以太坊网络” 展开,全程可复制粘贴执行。


1. 一键编译:把 .sol 文件变成 .java

1.1 加入 web3j-maven-plugin

在项目根目录 pom.xml<build><plugins> 节点内插入:

<plugin>
    <groupId>org.web3j</groupId>
    <artifactId>web3j-maven-plugin</artifactId>
    <version>4.8.7</version>
    <configuration>
        <packageName>com.demo.contract</packageName>
        <sourceDestination>src/main/java</sourceDestination>
        <soliditySourceFiles>
            <directory>src/main/resources/solc</directory>
            <includes>
                <include>**/*.sol</include>
            </includes>
        </soliditySourceFiles>
    </configuration>
</plugin>

1.2 把 ERC20.sol 放进去

新建目录 src/main/resources/solc/ERC20.sol,代码文末有完整示例,可直接复制。

终端定位到 pom.xml 同级目录,执行:

mvn web3j:generate-sources

看到 BUILD SUCCESS 后,com.demo.contract.ERC20.java 已自动生成,里面包含 部署函数 deploy所有 view 函数


2. 初始化:建立与以太坊的连接

2.1 引入依赖

确保已加载 Web3j、Spring Boot Starter:

<dependency>
    <groupId>org.web3j</groupId>
    <artifactId>web3j-spring-boot-starter</artifactId>
    <version>4.0.3</version>
</dependency>

2.2 application.yml 配置节点

web3j:
  client-address: https://mainnet.infura.io/v3/YOUR_PROJECT_ID   # 或本地私链
spring:
  logging:
    level:
      org.web3j.protocol: DEBUG

3. 部署合约:一行 sendAsync() 上链

@Autowired
private Web3j web3j;

private static final Credentials CREDENTIALS =
        Credentials.create("0x534d8d93a5ef661..."); // 私钥请务必保密

public String deployToken(String coinName, String symbol, long total) throws Exception {
    ERC20 erc20 = ERC20.deploy(
            web3j,
            CREDENTIALS,
            new DefaultGasProvider(),
            coinName,
            BigInteger.valueOf(total),
            symbol
    ).sendAsync().get();                               // 异步提交交易
    return erc20.getContractAddress();
}

想快速体验?直接调用;嫌弃网络慢?👉 十分钟搭建一条本地以太坊私链,gas 费用自己做主!


4. 加载并交互:查询、转账、授权全套代码

场景一句话调函数
查总发行量erc20.totalSupply().send()
查账户余额erc20.balanceOf(address).send()
普通转账erc20.transfer(to, amount).send()
授权额度erc20.approve(spender, amount).send()
查询授权额度erc20.allowance(owner, spender).send()

4.1 REST 接口截取片段

@GetMapping("/balance")
public BigInteger getBalance(
        @RequestParam String contractAddress,
        @RequestParam String accountAddress) throws Exception {
    ERC20 erc20 = ERC20.load(contractAddress, web3j, CREDENTIALS, new DefaultGasProvider());
    return erc20.balanceOf(accountAddress).sendAsync().get();
}

本地 Swagger 启动即可验证,完整源码文末已贴。


5. ERC20.sol 源码(可直接再编译)

pragma solidity ^0.4.24;

library SafeMath { ... }       // 略,见原文

contract ERC20 {
    using SafeMath for uint256;

    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowed;

    uint256 private _totalSupply;
    string public _coinName;
    string public _symbol;
    uint8 public decimals = 18;

    constructor(
        string coinName,
        uint256 totalSupply,
        string symbol
    ) public {
        _coinName = coinName;
        _symbol = symbol;
        _totalSupply = totalSupply * 10**uint256(decimals);
        _balances[msg.sender] = _totalSupply;
    }

    function totalSupply() public view returns (uint256) { return _totalSupply; }
    function balanceOf(address owner) public view returns (uint256) { return _balances[owner]; }

    function transfer(address to, uint256 value) public returns (bool) {
        require(to != address(0));
        require(value <= _balances[msg.sender]);
        _balances[msg.sender] = _balances[msg.sender].sub(value);
        _balances[to] = _balances[to].add(value);
        emit Transfer(msg.sender, to, value);
        return true;
    }

    function approve(address spender, uint256 value) public returns (bool) {
        _allowed[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }

    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowed[owner][spender];
    }

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

6. 真机实战:三台机器跑同一份合约

  1. 机器 A(PC)作为写链节点,部署合约、产 Token。
  2. 机器 B(服务器)定时调用 totalSupply() 把数据写入 MySQL。
  3. 机器 C(移动 App)只做 balanceOf() + transfer(),执行小额支付。

数据贯通后,每分钟交易数、平均 gas 价格、Token 进出账等实时图表一目了然。👉 零门槛获取链上实时大数据,可视化工具在这


7. 常见问题 FAQ

Q1:测试网络太多,选哪个?
A:GoerliSepolia 依旧是稳定性最高的公共测试网;私链请用 Ganache CLI,两秒钟就能挖出新区块。

Q2:为什么部署失败,提示 Intrinsic gas too low
A:Gas Limit 不足所致。Web3j 4.8.7 自带的 DefaultGasProvider 默认 4,300,000 gas,手动提升到 6,000,000 即可。

Q3:Java 是非异步友好语言,如何处理并发?
A:推荐用 CompletableFuture 包裹所有 .sendAsync(),配合 Spring WebFlux,一串 mono.zip() 即可完成链上链下协同调用。

Q4:私钥放在代码里安全吗?
A:根本不安全!部署 Key Vault、配置文件 Encrypt 或硬件钱包签名后再广播交易才是生产实践。

Q5:如何用单元测试穿越整条链路?
A:2 行即可启动 testcontainers-web3j,在 Docker 中跑一条短暂私链,每次跑完自动销毁,CI/CD 友好。


8. 打包命令速查表

# 编译
mvn clean web3j:generate-sources compile -DskipTests

# 测试
mvn test -Dtest=*

# 容器镜像
mvn spring-boot:build-image

小结

完成以上步骤后,你就拥有了与全球 以太坊网络 实时交互的 Java 服务,想进一步扩展?先看懂这里,再去研究 二层网络跨链桥 吧!