以太坊智能合約開發互動教程:從基礎到實際部署的完整指南
本文提供完整的以太坊智能合約開發互動教程,涵蓋開發環境設定、第一個智能合約、合約部署流程,並提供可直接在瀏覽器中運行的程式碼範例。我們從 Hardhat 開發框架的安裝和配置開始,逐步教學如何編寫、測試和部署智能合約,並展示如何使用 Web3.js 與合約進行互動。同時提供常見錯誤的調試方法和進階合約模式的實際應用。
以太坊智能合約開發互動教程:從基礎到實際部署
目錄
- 開發環境設定
- 第一個智能合約:簡易儲存合約
- 合約部署流程
- 互動式程式碼範例
- 常見錯誤與調試
- 進階合約模式
1. 開發環境設定
安裝必要工具
在进行以太坊智能合约开发之前,需要配置开发环境。以下是推荐的工具组合:
Node.js 環境
# 使用 nvm 安裝 Node.js 18+
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 18
nvm use 18
node --version
Hardhat 開發框架
Hardhat 是目前最流行的以太坊开发框架,提供了完整的开发、测试和部署工具链:
# 建立專案目錄
mkdir my-ethereum-dapp
cd my-ethereum-dapp
npm init -y
# 安裝 Hardhat
npm install --save-dev hardhat
# 初始化 Hardhat 專案
npx hardhat init
# 選擇 "Create a JavaScript project"
必要的依賴套件
# 安裝其他必要工具
npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install ethers@^6.11.0
npm install dotenv
本地測試網路
Hardhat 內建了本地区块链网络,可以用于本地测试:
// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.19",
networks: {
hardhat: {
chainId: 31337,
},
localhost: {
url: "http://127.0.0.1:8545",
},
},
};
啟動本地区块链网络:
npx hardhat node
# 這會啟動一個本地節點,預設提供 20 個測試帳戶
# 每個帳戶有 10000 ETH 的測試資金
2. 第一個智能合約:簡易儲存合約
合約代碼
以下是一個簡單的 value 儲存合約示範,展示了智能合約的基本結構:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/**
* @title SimpleStorage
* @dev 簡易值儲存合約
* @custom:dev-run-script ./scripts/deploy.js
*/
contract SimpleStorage {
// 狀態變數
uint256 private storedValue;
address public owner;
// 事件
event ValueChanged(uint256 newValue);
event OwnerChanged(address oldOwner, address newOwner);
// 修飾符
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}
// 建構函數
constructor() {
owner = msg.sender;
}
/**
* @dev 儲存一個值
* @param _value 要儲存的值
*/
function store(uint256 _value) public onlyOwner {
storedValue = _value;
emit ValueChanged(_value);
}
/**
* @dev 讀取儲存的值
* @return 儲存的值
*/
function retrieve() public view returns (uint256) {
return storedValue;
}
/**
* @dev 轉移所有權
* @param newOwner 新的所有者地址
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Invalid address");
address oldOwner = owner;
owner = newOwner;
emit OwnerChanged(oldOwner, newOwner);
}
}
合約部署腳本
// scripts/deploy.js
const hre = require("hardhat");
async function main() {
console.log("開始部署 SimpleStorage 合約...");
// 取得部署帳戶
const [deployer] = await hre.ethers.getSigners();
console.log("部署帳戶:", deployer.address);
console.log("帳戶餘額:", (await deployer.provider.getBalance(deployer.address)).toString());
// 部署合約
const SimpleStorage = await hre.ethers.getContractFactory("SimpleStorage");
const simpleStorage = await SimpleStorage.deploy();
await simpleStorage.waitForDeployment();
const contractAddress = await simpleStorage.getAddress();
console.log("SimpleStorage 合約已部署到:", contractAddress);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
部署合約:
npx hardhat run scripts/deploy.js --network hardhat
3. 合約部署流程
部署到不同網路
部署到本地網路
npx hardhat run scripts/deploy.js --network localhost
部署到 Sepolia 測試網路
首先需要在 .env 檔案中設定私鑰:
# .env
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_INFURA_KEY
PRIVATE_KEY=YOUR_WALLET_PRIVATE_KEY
更新 hardhat.config.js:
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
},
},
執行部署:
npx hardhat run scripts/deploy.js --network sepolia
部署到以太坊主網
npx hardhat run scripts/deploy.js --network mainnet
驗證部署的合約
部署完成後,可以在 Etherscan 上驗證合約源代碼:
npx hardhat verify --network sepolia CONTRACT_ADDRESS
4. 互動式程式碼範例
4.1 基本操作互動教學
以下程式碼範例展示了如何與已部署的合約進行互動:
// scripts/interact.js
const hre = require("hardhat");
// 合約 ABI(部分)
const ABI = [
"function store(uint256 _value) external",
"function retrieve() external view returns (uint256)",
"function owner() external view returns (address)"
];
async function main() {
// 連接到本地網路
const provider = new hre.ethers.JsonRpcProvider("http://127.0.0.1:8545");
// 使用測試帳戶
const wallet = hre.ethers.Wallet.fromMnemonic(
"test test test test test test test test test test test junk"
).connect(provider);
// 合約地址(需要替換為實際部署地址)
const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
// 建立合約實例
const contract = new hre.ethers.Contract(contractAddress, ABI, wallet);
// 讀取當前值
console.log("當前儲存值:", await contract.retrieve());
// 儲存新值
console.log("正在儲存值 42...");
const tx = await contract.store(42);
await tx.wait();
// 確認儲存結果
console.log("儲存後的值:", await contract.retrieve());
}
main();
4.2 自動執行合約
這個範例展示如何設定定時或條件觸發的自動合約操作:
// contracts/AutomatedContract.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
/**
* @title AutomatedStore
* @dev 自動化儲存合約,支援條件觸發
*/
contract AutomatedStore is Ownable, ReentrancyGuard {
uint256 public storedValue;
uint256 public lastUpdateTime;
uint256 public updateCount;
// 事件
event AutoUpdated(uint256 value, uint256 timestamp);
event ConditionMet(bool condition, uint256 value);
constructor() Ownable(msg.sender) {}
/**
* @dev 儲存值並記錄時間戳
*/
function store(uint256 _value) external onlyOwner nonReentrant {
storedValue = _value;
lastUpdateTime = block.timestamp;
updateCount++;
}
/**
* @dev 檢查是否滿足特定條件
* @param threshold 閾值
* @return 是否滿足條件
*/
function checkCondition(uint256 threshold) external view returns (bool) {
bool met = storedValue >= threshold;
emit ConditionMet(met, storedValue);
return met;
}
/**
* @dev 批量儲存多個值
* @param values 值陣列
*/
function batchStore(uint256[] calldata values) external onlyOwner nonReentrant {
require(values.length <= 100, "Too many values");
for (uint256 i = 0; i < values.length; i++) {
storedValue = values[i];
lastUpdateTime = block.timestamp;
updateCount++;
}
}
}
4.3 多重簽名錢包
以下是一個簡化的多重簽名錢包合約範例:
// contracts/MultiSigWallet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/**
* @title SimpleMultiSig
* @dev 簡易多重簽名錢包
*/
contract SimpleMultiSig {
address[] public owners;
uint256 public required;
struct Transaction {
address to;
uint256 value;
bytes data;
bool executed;
uint256 confirmationCount;
}
Transaction[] public transactions;
mapping(uint256 => mapping(address => bool)) public confirmations;
event SubmitTransaction(address indexed owner, uint256 indexed txIndex);
event ConfirmTransaction(address indexed owner, uint256 indexed txIndex);
event ExecuteTransaction(address indexed owner, uint256 indexed txIndex);
constructor(address[] memory _owners, uint256 _required) {
require(_owners.length > 0, "No owners");
require(_required > 0 && _required <= _owners.length, "Invalid required");
owners = _owners;
required = _required;
}
function submitTransaction(address _to, uint256 _value, bytes memory _data)
public
{
uint256 txIndex = transactions.length;
transactions.push(Transaction({
to: _to,
value: _value,
data: _data,
executed: false,
confirmationCount: 0
}));
emit SubmitTransaction(msg.sender, txIndex);
confirmTransaction(txIndex);
}
function confirmTransaction(uint256 _txIndex) public {
require(_txIndex < transactions.length, "Invalid tx");
require(!confirmations[_txIndex][msg.sender], "Already confirmed");
confirmations[_txIndex][msg.sender] = true;
transactions[_txIndex].confirmationCount++;
emit ConfirmTransaction(msg.sender, _txIndex);
if (transactions[_txIndex].confirmationCount >= required) {
executeTransaction(_txIndex);
}
}
function executeTransaction(uint256 _txIndex) public {
Transaction storage tx = transactions[_txIndex];
require(!tx.executed, "Already executed");
require(tx.confirmationCount >= required, "Not enough confirmations");
tx.executed = true;
(bool success, ) = tx.to.call{value: tx.value}(tx.data);
require(success, "Execution failed");
emit ExecuteTransaction(msg.sender, _txIndex);
}
receive() external payable {}
}
4.4 前端互動範例(Web3.js)
<!DOCTYPE html>
<html>
<head>
<title>SimpleStorage 互動範例</title>
<script src="https://cdn.jsdelivr.net/npm/ethers@6.11.0/dist/ethers.umd.min.js"></script>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
.container { background: #f5f5f5; padding: 20px; border-radius: 8px; }
input, button { padding: 10px; margin: 5px 0; width: 100%; box-sizing: border-box; }
button { background: #627eea; color: white; border: none; cursor: pointer; }
button:hover { background: #4a6bc7; }
.result { margin-top: 20px; padding: 10px; background: #e8f5e9; border-radius: 4px; }
.error { background: #ffebee; }
</style>
</head>
<body>
<div class="container">
<h2>SimpleStorage 互動範例</h2>
<button id="connectBtn">連接錢包</button>
<div id="walletInfo" style="display:none;">
<p>錢包地址: <span id="address"></span></p>
<p>當前值: <span id="currentValue">載入中...</span></p>
<input type="number" id="valueInput" placeholder="輸入要儲存的值">
<button id="storeBtn">儲存值</button>
<button id="retrieveBtn">讀取值</button>
</div>
<div id="result"></div>
</div>
<script>
let contract;
// 合約地址和 ABI
const CONTRACT_ADDRESS = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
const CONTRACT_ABI = [
"function store(uint256 _value) external",
"function retrieve() external view returns (uint256)",
"event ValueChanged(uint256 newValue)"
];
async function connectWallet() {
if (typeof window.ethereum !== 'undefined') {
try {
await window.ethereum.request({ method: 'eth_requestAccounts' });
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
document.getElementById('address').textContent = await signer.getAddress();
document.getElementById('walletInfo').style.display = 'block';
document.getElementById('connectBtn').textContent = '已連接';
contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, signer);
await updateValue();
} catch (error) {
showResult('連接失敗: ' + error.message, true);
}
} else {
showResult('請安裝 MetaMask!', true);
}
}
async function storeValue() {
const value = document.getElementById('valueInput').value;
if (!value) {
showResult('請輸入值', true);
return;
}
try {
showResult('交易處理中...', false);
const tx = await contract.store(value);
await tx.wait();
showResult('儲存成功!', false);
await updateValue();
} catch (error) {
showResult('錯誤: ' + error.message, true);
}
}
async function retrieveValue() {
try {
const value = await contract.retrieve();
showResult('讀取值: ' + value.toString(), false);
} catch (error) {
showResult('錯誤: ' + error.message, true);
}
}
async function updateValue() {
try {
const value = await contract.retrieve();
document.getElementById('currentValue').textContent = value.toString();
} catch (error) {
document.getElementById('currentValue').textContent = '讀取失敗';
}
}
function showResult(message, isError) {
const resultDiv = document.getElementById('result');
resultDiv.textContent = message;
resultDiv.className = 'result' + (isError ? ' error' : '');
}
document.getElementById('connectBtn').addEventListener('click', connectWallet);
document.getElementById('storeBtn').addEventListener('click', storeValue);
document.getElementById('retrieveBtn').addEventListener('click', retrieveValue);
</script>
</body>
</html>
5. 常見錯誤與調試
5.1 常見編譯錯誤
Solidity 版本衝突
Error: Compiler run failed
ParserError: Source file requires different compiler version
解決方案:確保 hardhat.config.js 中的編譯器版本與合約中指定的版本匹配。
類型錯誤
// 錯誤範例
uint8 public value = 256; // uint8 最大值為 255
// 正確
uint16 public value = 256;
5.2 部署常見問題
Insufficient Balance
Error: insufficient funds for gas * price + value
解決方案:確保帳戶有足夠的 ETH 支付 Gas 費用。在測試網路上可以使用水龍頭獲取測試 ETH。
nonce 錯誤
Error: nonce too low
解決方案:重置帳戶的 nonce 或等待交易確認。
5.3 調試技巧
使用 Hardhat Console
npx hardhat console --network localhost
在 console 中可以直接與合約互動:
const [owner] = await ethers.getSigners();
const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
const contract = await SimpleStorage.deploy();
await contract.waitForDeployment();
await contract.store(100);
console.log(await contract.retrieve());
事件監聽
// 監聽合約事件
contract.on("ValueChanged", (newValue, event) => {
console.log("值已更改:", newValue.toString());
});
6. 進階合約模式
6.1 可升級合約模式
// contracts/UpgradeableStorage.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/**
* @title Initializable
* @dev 可升級合約基礎
*/
abstract contract Initializable {
bool private _initialized;
modifier initializer() {
require(!_initialized, "Already initialized");
_;
_initialized = true;
}
}
/**
* @title UpgradeableStore
* @dev 可升級的儲存合約
*/
contract UpgradeableStore is Initializable {
uint256 public value;
address public owner;
function initialize() public initializer {
owner = msg.sender;
}
function setValue(uint256 _value) external {
require(msg.sender == owner, "Not owner");
value = _value;
}
function getValue() external view returns (uint256) {
return value;
}
}
6.2 速率限制模式
// contracts/RateLimited.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/**
* @title RateLimited
* @dev 速率限制合約
*/
abstract contract RateLimited {
uint256 public rateLimit;
uint256 public lastUpdate;
uint256 public available;
constructor(uint256 _rateLimit) {
rateLimit = _rateLimit;
lastUpdate = block.timestamp;
available = _rateLimit;
}
function _consume(uint256 amount) internal {
uint256 elapsed = block.timestamp - lastUpdate;
available += elapsed * (rateLimit / 3600);
if (available > rateLimit) available = rateLimit;
require(available >= amount, "Rate limit exceeded");
available -= amount;
lastUpdate = block.timestamp;
}
}
總結
本教學涵蓋了以太坊智能合約開發的核心知識,從環境設定、合約編寫、部署流程到實際互動操作。透過這些範例,讀者應該能夠:
- 正確配置 Hardhat 開發環境
- 編寫和部署基本的智能合約
- 使用 JavaScript 與合約進行互動
- 理解常見錯誤並掌握調試技巧
- 應用進階合約模式解決實際問題
建議讀者實際動手操作這些範例,體驗以太坊智能合約開發的完整流程。
相關文章
- 以太坊智能合約開發實作教程:從環境搭建到部署上線完整指南 — 本教程帶領讀者從零開始,建立完整的以太坊開發環境,撰寫第一個智能合約,並將其部署到測試網絡和主網。我們使用 Solidity 作為合約語言,Hardhat 作為開發框架,提供一步一步的詳細操作指南。內容涵蓋 Hardhat 專案初始化、ERC-20 代幣合約撰寫、單元測試、Sepolia 測試網絡部署、以及主網部署等完整流程。
- 以太坊智能合約開發實戰:從基礎到 DeFi 協議完整代碼範例指南 — 本文提供以太坊智能合約開發的完整實戰指南,透過可直接運行的 Solidity 代碼範例,幫助開發者從理論走向實踐。內容涵蓋基礎合約開發、借貸協議實作、AMM 機制實現、以及中文圈特有的應用場景(台灣交易所整合、香港監管合規、Singapore MAS 牌照申請)。本指南假設讀者具備基本的程式設計基礎,熟悉 JavaScript 或 Python 等語言,並對區塊鏈概念有基本理解。
- 以太坊第一個 DApp 開發完整教學:從零到部署 — 帶領讀者從零開始,開發並部署第一個以太坊 DApp。詳細講解智慧合約開發、前端整合、錢包連接、測試網部署等完整流程,涵蓋 Hardhat、Solidity、React、Ethers.js 等主流開發工具。
- 以太坊智能合約部署完整實戰指南:從環境建置到主網上線 — 本文提供以太坊智能合約部署的完整實戰教學,涵蓋從開發環境建置、本地測試、測試網部署、到主網上線的全部流程。我們以實際的 ERC-20 代幣合約為例,逐步講解每個環節的技術細節、可能遇到的問題、以及最佳實踐建議,幫助開發者掌握智能合約部署的核心技能。
- 以太坊智能合約開發實戰完整教程:從環境建置到部署的工程實踐 — 本指南從工程師視角出發,提供完整的智能合約開發實戰教學。內容涵蓋主流開發框架 Hardhat 與 Foundry 的深度使用、測試驅動開發實踐、模糊測試工具應用、以及生產環境部署的最佳實踐,幫助開發者建立完善的智能合約開發工作流。
延伸閱讀與來源
- Ethereum.org Developers 官方開發者入口與技術文件
- EIPs 以太坊改進提案
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!