一步步教你基于以太坊发行 ERC-20 代币

·

适用人群:对 Solidity 有大致了解、准备快速上手 代币发行 的开发者与创业者
目标:在以太坊测试网独立部署一套具备转账、增发、黑白名单、自动兑换、Gas 代付六大功能的专属代币

一、代币核心概念与功能全景

什么是“代币”?

在以太坊语境中,代币 并非独立链,而是通过 智能合约 发行的数字资产,符合 ERC-20 标准即可在各大钱包与交易所通用。
你只需编写一段约 150 行的 Solidity 代码,即可定义名称、总量、图标、小数位数,以及转账、冻结、增发、挖矿、兑换、Gas 代付等复杂逻辑。

六步看清代币能力地图

  1. 基础资产:名称、符号、总量、转账。
  2. 管理者权限:合约拥有者具备增发、冻结、参数调整特权。
  3. 黑白名单:冻结或解冻指定地址,资产在链上但无法流通。
  4. 通胀机制:随时 mint 增发,一键扩展代币经济模型
  5. 自动阶梯兑换:设定买卖价差,做“链上银行”赚手续费。
  6. Gas 代付:合约先垫付 ETH,再从用户余额扣回,降低新手门槛。

👉 一篇看完“链上发币”所有细节和坑点


二、十分钟部署:基本功能完整合约

下面是最精简的 MyToken 示例,满足 ERC-20“转账+查询”两大底层要求。

2.1 代码速读

pragma solidity ^0.8.0;

contract MyToken {
    string  public name;
    string  public symbol;
    uint8   public decimals;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;

    event Transfer(address indexed from, address indexed to, uint256 value);

    constructor(
        uint256 _initialSupply,
        string memory _tokenName,
        uint8   _decimals,
        string memory _tokenSymbol
    ){
        totalSupply = _initialSupply * 10 ** _decimals;
        balanceOf[msg.sender] = totalSupply;
        name   = _tokenName;
        symbol = _tokenSymbol;
        decimals = _decimals;
    }

    function transfer(address _to, uint256 _value) public returns (bool){
        require(balanceOf[msg.sender] >= _value, "Insufficient balance");
        balanceOf[msg.sender] -= _value;
        balanceOf[_to]       += _value;
        emit Transfer(msg.sender, _to, _value);
        return true;
    }
}

2.2 测试网部署脚本

  1. 打开 Remix IDE,选择 “Solidity Compiler”,版本 0.8.x。
  2. Environment 切换为 “Injected Provider”,连接 MetaMask 测试网(Goerli、Sepolia 均可)。
  3. 填入 initialSupply=1000decimals=18、其它随意,点击 Deploy
  4. 在区块浏览器输入合约地址,你会发现 余额一览无遗

2.3 合约验证与 Watch Token


三、超能力进阶:六大高级功能深度拆解

功能作用典型场景
管理者权限只有 owner 可增发、调价差、冻结等DAO 治理、官方安全
增发机制mint 函数可随时增量,而非固定总量游戏积分扩张
黑白名单freezeAccount() 一键冻结/解冻反洗钱、合规要求
自动买卖合约以 sellPrice/buyPrice 与 ETH 交换代币,价差即利润去中心化 DEX 入口
Gas 代付用户在无 ETH 场景下也能转账,由合约垫付 ETH零门槛积分兑换

3.1 管理角色模块化

// Owned.sol 管理基类
contract Owned {
    address public owner;
    constructor() { owner = msg.sender; }
    modifier onlyOwner {
        require(msg.sender == owner, "Not owner");
        _;
    }
    function transferOwnership(address newOwner) external onlyOwner {
        owner = newOwner;
    }
}

所有后续合约通过 import "./Owned.sol"; + contract MyToken is Owned {} 直接继承,一行解决权限问题。

3.2 增发函数示例

function mint(address _target, uint256 _mintAmount) external onlyOwner {
    balanceOf[_target] += _mintAmount;
    totalSupply        += _mintAmount;
    emit Transfer(address(0), _target, _mintAmount);
}

注意:任何增发需向社会公示逻辑,否则容易引发信任危机。

3.3 黑白名单实现

mapping (address => bool) public frozenAccount;
event FrozenFunds(address indexed target, bool frozen);

function freezeAccount(address _target, bool _freeze) external onlyOwner {
    frozenAccount[_target] = _freeze;
    emit FrozenFunds(_target, _freeze);
}

发起转账时,只需在前置逻辑添加:

require(!frozenAccount[msg.sender], "Account frozen");

3.4 链上自动兑换

uint256 public buyPrice  = 1e15;   // 1 token = 0.001 ETH
uint256 public sellPrice = 8e14;  // 卖出 0.8 * 0.001 ETH

function buy() external payable {
    uint256 amount = msg.value / buyPrice;
    require(balanceOf[address(this)] >= amount, "Contract sold out");
    balanceOf[address(this)] -= amount;
    balanceOf[msg.sender]    += amount;
    emit Transfer(address(this), msg.sender, amount);
}

function sell(uint256 _amount) external {
    require(balanceOf[msg.sender] >= _amount, "Insufficient balance");
    uint256 ethAmount = _amount * sellPrice;
    balanceOf[msg.sender] -= _amount;
    balanceOf[address(this)] += _amount;
    payable(msg.sender).transfer(ethAmount);
    emit Transfer(msg.sender, address(this), _amount);
}

如需调整价差,管理员执行:

function setPrices(uint256 _buy, uint256 _sell) external onlyOwner {
    buyPrice  = _buy;
    sellPrice = _sell;
}

3.5 Gas 代付逻辑

逻辑为:用户无需 Pre-fuel ETH,合约发起 Meta-Tx,再由合约回收代币作为矿工费抵偿。

uint256 public gasReserve = 1 ether;

modifier gasRelay {
    uint startGas = gasleft();
    _;
    uint usedGas  = 21000 + startGas - gasleft();
    uint gasEth   = tx.gasprice * usedGas;
    require(balanceOf[msg.sender] * sellPrice >= gasEth, "Cannot pay gas");
    // 扣除对应代币并转给矿工作为垫付补偿
}

注意:本功能最好配合 EIP-2771 标准实现,以降低签名复杂度。


四、实战手把手:从编译到上链

  1. 本地环境:npm install -g truffle + truffle init 初始化项目。
  2. 文件结构:

    • contracts/MyToken.sol
    • contracts/Owned.sol
    • migrations/2_deploy_mytoken.js
  3. 配置 truffle-config.js 加入 Goerli RPC 与私钥(切记 .env 保护私钥)。
  4. 部署:truffle migrate --network goerli
  5. 开源:在 Etherscan 填写相应的构造函数 ABI 与优化勾选,绿色 Checkmark 提升 代币搜索排名

👉 领取手把手部署模板 & 开源示例


五、踩坑与优化清单


六、FAQ:开发者最常追问的五件事

Q1:只需将 ERC-20 合约部署到主网就算完成发币吗?
A:技术上完成 50%。真正流通还需要 添加流动性池、做市规则、社区治理文档以及多钱包图标申请。

Q2:黑白名单会不会破坏“去中心化”卖点?
A:引入 onlyOwner 是为了合规与安全。理想做法是在未来加入 DAO 投票冻结,逐步削弱中心化权力。

Q3:代币可以在中心化交易所上架吗?
A:只要通过法务审查与审计,即可申请币安、OKX 等主流平台。流动性深度与社区活跃程度是交易所主要考核指标。

Q4:为什么我 Remix 编译总报 Gas Estimation Error?
A:十有八九是构造函数参数位数与 require 判定冲突,例如将 uint8 位数的 decimals误传字符串。

Q5:增发脚本可以直接在主网调用吗?
A:建议配合 Gnosis Safe 多重签名,至少 3/5 私钥授权,防止单点失误造成通货膨胀。


结语:把创意写成合约,把信任写进区块

从一行 Solidity 到一次主网交易,你已完成“从 0 到 1”的跨越。
下一阶段,才是真正 代币经济模型社区运营 的马拉松。祝你在去中心化的星辰大海,一路高歌、少一些 Rug、多一些传奇!