發行 ERC-20 代幣

ERC-20 是以太坊上最廣泛使用的代幣標準。所有在以太坊上的加密货币——USDT、UNI、LINK——都是 ERC-20 代幣。

ERC-20 標準介面

一個標準的 ERC-20 代幣必須實現以下功能:

| 函式 | 說明 | |------|------| | totalSupply() | 回傳代幣總供應量 | | balanceOf(address) | 查詢指定地址的餘額 | | transfer(to, amount) | 轉帳 | | approve(spender, amount) | 授權他人動用你的代幣 | | transferFrom(from, to, amount) | 在授權範圍內代扣轉帳 | | allowance(owner, spender) | 查詢授權額度 |

使用 OpenZeppelin 發行代幣

OpenZeppelin 提供了經過嚴格審計的標準合約實作。我們不需要從零寫起,直接繼承即可:

// contracts/VibeToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";

contract VibeToken is ERC20, ERC20Burnable, Ownable, ERC20Pausable {
    // 交易稅率(3% = 300,基數為 10000)
    uint256 public constant TAX_RATE = 300;
    uint256 public constant TAX_DENOMINATOR = 10000;
    
    // 金庫地址(稅金收集地址)
    address public treasuryAddress;
    
    // 免稅地址列表
    mapping(address => bool) public isExcludedFromTax;
    
    // 事件
    event TaxCharged(address indexed from, address indexed to, uint256 amount, uint256 tax);
    event TreasuryUpdated(address indexed newTreasury);
    event TaxExclusionUpdated(address indexed account, bool excluded);
    
    constructor(
        address _treasuryAddress
    ) ERC20("Vibe Token", "VIBE") Ownable(msg.sender) {
        require(_treasuryAddress != address(0), "金庫地址不能為空");
        treasuryAddress = _treasuryAddress;
        
        // 鑄造 1,000,000 枚代幣給合約擁有者
        uint256 initialSupply = 1_000_000 * 10 ** decimals();
        _mint(msg.sender, initialSupply);
        
        // 合約擁有者免稅
        isExcludedFromTax[msg.sender] = true;
    }
    
    // 覆寫 decimals,設為 18 位(ERC-20 標準)
    function decimals() public pure virtual override returns (uint8) {
        return 18;
    }
    
    // 覆寫 transfer 以加入交易稅
    function transfer(
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        address owner = _msgSender();
        
        if (isExcludedFromTax[owner] || isExcludedFromTax[to]) {
            // 免稅轉帳
            return super.transfer(to, amount);
        }
        
        // 計算稅金
        uint256 tax = (amount * TAX_RATE) / TAX_DENOMINATOR;
        uint256 amountAfterTax = amount - tax;
        
        // 扣稅
        super.transfer(treasuryAddress, tax);
        emit TaxCharged(owner, to, amountAfterTax, tax);
        
        // 轉帳剩餘
        return super.transfer(to, amountAfterTax);
    }
    
    // 覆寫 transferFrom
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        if (isExcludedFromTax[from] || isExcludedFromTax[to]) {
            return super.transferFrom(from, to, amount);
        }
        
        uint256 tax = (amount * TAX_RATE) / TAX_DENOMINATOR;
        uint256 amountAfterTax = amount - tax;
        
        super.transferFrom(from, treasuryAddress, tax);
        emit TaxCharged(from, to, amountAfterTax, tax);
        
        return super.transferFrom(from, to, amountAfterTax);
    }
    
    // 設定金庫地址
    function updateTreasury(address _newTreasury) external onlyOwner {
        require(_newTreasury != address(0), "金庫地址不能為空");
        treasuryAddress = _newTreasury;
        emit TreasuryUpdated(_newTreasury);
    }
    
    // 設定免稅地址
    function setTaxExclusion(address account, bool excluded) external onlyOwner {
        isExcludedFromTax[account] = excluded;
        emit TaxExclusionUpdated(account, excluded);
    }
    
    // 鑄造新代幣
    function mint(address to, uint256 amount) external onlyOwner {
        _mint(to, amount);
    }
    
    // 暫停所有轉帳(緊急情況)
    function pause() external onlyOwner {
        _pause();
    }
    
    function unpause() external onlyOwner {
        _unpause();
    }
    
    // OpenZeppelin 需要這個來處理多繼承
    function _update(
        address from,
        address to,
        uint256 value
    ) internal override(ERC20, ERC20Pausable) {
        super._update(from, to, value);
    }
}

編譯與部署

// scripts/deploy_token.js
const hre = require("hardhat");

async function main() {
  const [deployer] = await hre.ethers.getSigners();
  
  console.log("部署者地址:", deployer.address);
  console.log("部署者餘額:", hre.ethers.formatEther(
    await hre.ethers.provider.getBalance(deployer.address)
  ), "ETH");
  
  // 部署 VibeToken
  const VibeToken = await hre.ethers.getContractFactory("VibeToken");
  const token = await VibeToken.deploy(deployer.address);
  await token.waitForDeployment();
  
  const tokenAddress = await token.getAddress();
  console.log("VibeToken 部署地址:", tokenAddress);
  
  // 查詢總供應量
  const totalSupply = await token.totalSupply();
  console.log("總供應量:", hre.ethers.formatEther(totalSupply), "VIBE");
  
  // 查詢部署者餘額
  const balance = await token.balanceOf(deployer.address);
  console.log("部署者餘額:", hre.ethers.formatEther(balance), "VIBE");
  
  // 寫入部署資訊供前端使用
  const fs = require("fs");
  const deploymentInfo = {
    tokenAddress: tokenAddress,
    deployer: deployer.address,
    totalSupply: hre.ethers.formatEther(totalSupply),
    network: hre.network.name,
  };
  
  fs.writeFileSync(
    "deployment_info.json",
    JSON.stringify(deploymentInfo, null, 2)
  );
  console.log("部署資訊已儲存到 deployment_info.json");
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
# 部署到本地測試網
npx hardhat run scripts/deploy_token.js

# 部署到 Sepolia 測試網
npx hardhat run scripts/deploy_token.js --network sepolia

在 Etherscan 驗證合約

# 安裝 hardhat-etherscan
npm install --save-dev @nomicfoundation/hardhat-verify

# 在 hardhat.config.js 中加入 Etherscan API Key
// hardhat.config.js
etherscan: {
  apiKey: process.env.ETHERSCAN_API_KEY
}
npx hardhat verify --network sepolia <合約地址> <金庫地址>

使用 Vibe Coding 發行代幣

🔥 【代幣發行詠唱範例】 「請幫我發行一個 ERC-20 代幣,包含以下功能: 1. 代幣名稱 MyToken,代號 MTK,總量 10 億枚。 2. 每筆交易收取 2% 手續費,自動銷毀 (burn)。 3. 擁有者可以鑄造新代幣。 4. 擁有者可以暫停交易。 5. 部署到 Sepolia 測試網。 6. 在 Etherscan 上驗證合約。」

本日總結

在本章中,你學到了:

  1. ERC-20 標準:代幣的統一介面規範
  2. OpenZeppelin 庫:使用經過審計的標準合約
  3. 交易稅機制:每筆交易自動扣稅轉到金庫
  4. 鑄造與銷毀:控制代幣的供應量
  5. 緊急暫停:Pausable 機制
  6. 部署與驗證:部署到測試網並在 Etherscan 驗證

下一章,我們將發行自己的 NFT!

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!