Scaffold-ETH 完整開發指南:從零開始構建以太坊應用

系統介紹 Scaffold-ETH 框架的設計理念、核心組件和實戰開發流程,從環境搭建、智慧合約開發到前端集成,幫助開發者快速構建全棧以太坊去中心化應用。

Scaffold-ETH 完整開發指南:從零開始構建以太坊應用

概述

Scaffold-ETH 是以太坊生態系統中最受歡迎的應用開發框架之一,由 Austin Griffith 創建並獲得廣泛社區支持。這個框架旨在幫助開發者快速搭建全棧以太坊應用,將智慧合約開發與前端介面整合在一起,大幅縮短從概念驗證到實際部署的時間。

本文深入介紹 Scaffold-ETH 的設計理念、核心組件、實戰開發流程,以及如何利用這個框架快速構建去中心化應用。我們將從環境搭建開始,逐步引導讀者完成一個完整的 DeFi 應用開發流程。

一、Scaffold-ETH 架構解析

1.1 設計理念

Scaffold-ETH 的設計圍繞「快速原型開發」這一核心目標:

1.2 核心組件

Scaffold-ETH 由多個相互協作的組件構成:

Scaffold-ETH 架構:

┌─────────────────────────────────────────────────────────────┐
│                    前端層 (Next.js/React)                    │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │   Frontend  │  │  App Routes │  │  Hooks &    │        │
│  │  Components │  │  ( Wagmi )  │  │  Utils      │        │
│  └─────────────┘  └─────────────┘  └─────────────┘        │
├─────────────────────────────────────────────────────────────┤
│                   錢包連接層 (RainbowKit/Wagmi)               │
├─────────────────────────────────────────────────────────────┤
│                  智慧合約層 (Hardhat/Foundry)                │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │   Solidity  │  │ Deploy      │  │  Contract   │        │
│  │   Contracts │  │   Scripts   │  │  ABIs       │        │
│  └─────────────┘  └─────────────┘  └─────────────┘        │
└─────────────────────────────────────────────────────────────┘

1.3 版本選擇

Scaffold-ETH 目前有兩個主要版本:

Scaffold-ETH 2(當前主流)

Scaffold-ETH 1(經典版本)

建議新專案使用 Scaffold-ETH 2。

二、環境準備與專案初始化

2.1 系統要求

在開始之前,確保你的開發環境滿足以下要求:

2.2 安裝流程

第一步:Clone 專案

# 進入工作目錄
cd ~/projects

# Clone Scaffold-ETH 2
git clone https://github.com/scaffold-eth/scaffold-eth-2.git my-dapp

# 進入專案目錄
cd my-dapp

第二步:安裝依賴

# 安裝所有依賴
npm install

這個過程會安裝:

第三步:配置環境變數

# 複製環境範例檔案
cp .env.example .env

# 編輯環境變數
nano .env

必要的環境變數:

# Alchemy 或其他 RPC 提供者
SEPOLIA_RPC_URL=your_rpc_url_here
MAINNET_RPC_URL=your_rpc_url_here

# 錢包私鑰(用於部署,注意安全)
DEPLOYER_PRIVATE_KEY=your_private_key_here

# Etherscan API Key(用於驗證合約)
ETHERSCAN_API_KEY=your_etherscan_key

2.3 專案結構

初始化後的專案結構如下:

my-dapp/
├── packages/
│   ├── nextjs/                 # Next.js 前端應用
│   │   ├── app/               # App Router
│   │   │   ├── page.tsx       # 主頁面
│   │   │   └── layout.tsx     # 佈局
│   │   ├── components/        # React 元件
│   │   │   ├── scaffold/      # Scaffold 內建元件
│   │   │   └── ui/           # 自定義元件
│   │   ├── hooks/             # 自定義 Hooks
│   │   ├── utils/             # 工具函數
│   │   └── scaffold.config.ts # 前端配置
│   │
│   └── hardhat/               # Hardhat 合約項目
│       ├── contracts/         # Solidity 合約
│       │   └── YourContract.sol
│       ├── deploy/            # 部署腳本
│       ├── test/              # 測試檔案
│       └── hardhat.config.ts # Hardhat 配置
│
├── .env                       # 環境變數
├── package.json              # 根 package.json
└── README.md                 # 專案說明

三、智慧合約開發

3.1 創建第一個合約

packages/hardhat/contracts/ 目錄下創建新的 Solidity 合約:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
 * @title SimpleToken
 * @dev 簡單的 ERC20 代幣合約示例
 */
contract SimpleToken is ERC20 {
    uint256 public constant INITIAL_SUPPLY = 1000000 * 10**18;

    /**
     * @dev 部署時鑄造全部供應量到部署者帳戶
     */
    constructor() ERC20("Simple Token", "SIM") {
        _mint(msg.sender, INITIAL_SUPPLY);
    }
}

3.2 合約部署

使用 Hardhat 部署

# 部署到本地 Hardhat 網絡
cd packages/hardhat
npx hardhat run deploy/00_deploy_your_contract.ts --network localhost

部署腳本示例(deploy/00_deploy_your_contract.ts):

import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
  const { deployer } = await hre.getNamedAccounts();
  const { deploy } = hre.deployments;

  // 部署合約
  await deploy("SimpleToken", {
    from: deployer,
    log: true,
    autoMine: true,
  });
};

export default func;
func.tags = ["SimpleToken"];

部署到測試網

# 部署到 Sepolia 測試網
npx hardhat run deploy/00_deploy_your_contract.ts --network sepolia

部署成功後,合約位址會自動同步到前端配置中。

3.3 合約交互

Scaffold-ETH 提供了便捷的合約交互方式。在前端代碼中:

// 使用 useScaffoldContract 鉤子獲取合約實例
import { useScaffoldContract } from "~~/hooks/scaffold-eth";

const { data: yourContract } = useScaffoldContract({
  contractName: "SimpleToken",
});

// 調用合約方法
const mintTokens = async () => {
  if (yourContract) {
    const tx = await yourContract.mint(amount);
    await tx.wait();
  }
};

// 讀取合約數據
const balance = await yourContract.balanceOf(address);

四、前端開發

4.1 錢包連接

Scaffold-ETH 內建了錢包連接功能,使用 RainbowKit 和 Wagmi:

// packages/nextjs/app/components/Header.tsx
"use client";

import { useAccount, useConnectors } from "wagmi";
import { ArrowRightOnRectangleIcon, CircleStackIcon } from "@heroicons/react/24/outline";

export const Header = () => {
  const { address, isConnected } = useAccount();
  const { connectors, connect } = useConnectors();

  return (
    <div className="navbar bg-base-100">
      <div className="flex-1">
        <a className="btn btn-ghost normal-case text-xl">My DApp</a>
      </div>
      <div className="flex-none">
        {isConnected ? (
          <div className="dropdown dropdown-end">
            <label tabIndex={0} className="btn btn-ghost btn-circle avatar">
              <div className="w-10 rounded-full bg-neutral">
                <CircleStackIcon className="h-6 w-6 text-white" />
              </div>
            </label>
            <ul tabIndex={0} className="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
              <li>
                <a>{address?.slice(0, 6)}...{address?.slice(-4)}</a>
              </li>
            </ul>
          </div>
        ) : (
          <button
            className="btn btn-primary"
            onClick={() => connect({ connector: connectors[0] })}
          >
            Connect Wallet
          </button>
        )}
      </div>
    </div>
  );
};

4.2 讀取合約數據

使用 useScaffoldReadContract 鉤子讀取合約狀態:

import { useScaffoldReadContract } from "~~/hooks/scaffold-eth";

export const TokenBalance = ({ address }: { address: string }) => {
  const { data: balance } = useScaffoldReadContract({
    contractName: "SimpleToken",
    functionName: "balanceOf",
    args: [address],
  });

  return (
    <div className="card bg-base-100 shadow-xl">
      <div className="card-body">
        <h2 className="card-title">Token Balance</h2>
        <p className="text-2xl font-bold">
          {balance ? Number(balance).toLocaleString() : "0"} SIM
        </p>
      </div>
    </div>
  );
};

4.3 寫入合約

使用 useScaffoldWriteContract 鉤子發起交易:

import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";

export const MintButton = () => {
  const { writeContractAsync } = useScaffoldWriteContract("SimpleToken");

  const handleMint = async () => {
    try {
      writeContractAsync({
        functionName: "mint",
        args: [BigInt(1000 * 10**18)],
      });
    } catch (err) {
      console.error("Mint failed:", err);
    }
  };

  return (
    <button className="btn btn-primary" onClick={handleMint}>
      Mint 1000 SIM
    </button>
  );
};

4.4 交易歷史

使用 useWatchContractEvent 監聽合約事件:

import { useWatchContractEvent } from "wagmi";
import { useScaffoldContractRead } from "~~/hooks/scaffold-eth";

export const TransactionHistory = () => {
  const [events, setEvents] = useState<any[]>([]);

  useWatchContractEvent({
    address: "0x...", // 合約地址
    abi: [...], // 合約 ABI
    eventName: "Transfer",
    onLogs(logs) {
      setEvents((prev) => [...prev, ...logs]);
    },
  });

  return (
    <div>
      <h3>Transaction History</h3>
      {events.map((event, i) => (
        <div key={i}>
          From: {event.args.from} To: {event.args.to} Amount: {event.args.value.toString()}
        </div>
      ))}
    </div>
  );
};

五、進階功能

5.1 自定義網絡配置

scaffold.config.ts 中配置自定義網絡:

import type { ScaffoldConfig } from "./scaffold.config";

const config: ScaffoldConfig = {
  targetNetworks: [
    // 本地網絡
    {
      id: 31337,
      name: "Localhost",
      httpUrl: "http://localhost:8545",
      wsUrl: "ws://localhost:8545",
    },
    // Sepolia 測試網
    {
      id: 11155111,
      name: "Sepolia",
      httpUrl: process.env.NEXT_PUBLIC_SEPOLIA_RPC_URL || "",
      wsUrl: process.env.NEXT_PUBLIC_SEPOLIA_RPC_URL?.replace("https", "wss"),
    },
  ],
  // 其他配置...
};

export default config;

5.2 事件監控儀表板

利用 Scaffold-ETH 構建簡單的事件監控儀表板:

"use client";

import { useState, useEffect } from "react";
import { usePublicClient } from "wagmi";
import { getContract } from "wagmi/actions";

export const EventDashboard = () => {
  const [recentEvents, setRecentEvents] = useState<any[]>([]);
  const publicClient = usePublicClient();

  useEffect(() => {
    const fetchEvents = async () => {
      const logs = await publicClient.getContractEvents({
        address: "0x...",
        abi: [...],
        eventName: "Transfer",
        fromBlock: 0n,
        toBlock: "latest",
      });
      setRecentEvents(logs.slice(-10).reverse());
    };

    fetchEvents();
  }, [publicClient]);

  return (
    <div className="overflow-x-auto">
      <table className="table">
        <thead>
          <tr>
            <th>Block</th>
            <th>From</th>
            <th>To</th>
            <th>Amount</th>
          </tr>
        </thead>
        <tbody>
          {recentEvents.map((event) => (
            <tr key={event.logIndex}>
              <td>{event.blockNumber.toString()}</td>
              <td>{event.args.from?.slice(0, 10)}...</td>
              <td>{event.args.to?.slice(0, 10)}...</td>
              <td>{event.args.value?.toString()}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

5.3 多簽錢包集成

集成 Safe(原 Gnosis Safe)多簽錢包:

import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk";

export const SafeMultiSig = () => {
  const { safe, sdk } = useSafeAppsSDK();

  const executeTransaction = async () => {
    const tx = {
      to: "0x...",
      data: "0x...",
      value: "0",
    };

    const safeTxHash = await sdk.txs.signMessage(tx);
    // 實際執行需要多簽審批
  };

  return (
    <button onClick={executeTransaction} className="btn btn-secondary">
      Execute via Safe
    </button>
  );
};

5.4 客製化主題

Scaffold-ETH 使用 Tailwind CSS,輕鬆實現主題定制:

/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --primary: #6366f1;
  --secondary: #ec4899;
  --accent: #10b981;
}

@layer components {
  .btn-primary {
    @apply bg-[var(--primary)] hover:bg-[var(--primary)]/90 text-white;
  }
}

六、本地開發工作流

6.1 啟動開發伺服器

# 同時啟動前端和 Hardhat 節點
npm run start

# 或者分開啟動

# 終端 1:啟動 Hardhat 本地節點
cd packages/hardhat
npx hardhat node

# 終端 2:啟動前端開發伺服器
cd packages/nextjs
npm run dev

6.2 熱重載

Scaffold-ETH 支援智慧合約修改後的熱重載:

  1. 修改 Solidity 合約代碼
  2. 重新部署合約:
   cd packages/hardhat
   npx hardhat deploy --network localhost
  1. 前端自動感知合約變化並刷新

6.3 除錯技巧

合約除錯

// 在合約中添加事件用於除錯
event DebugEvent(string message, uint256 value);

function someFunction(uint256 input) public {
    emit DebugEvent("Function called with value:", input);
    // 函數邏輯
}

前端除錯

// 使用 wagmi 的除錯功能
const { data, error } = useScaffoldReadContract({
  contractName: "YourContract",
  functionName: "yourFunction",
});

// 檢查錯誤
if (error) {
  console.error("Contract call error:", error);
}

七、部署上線

7.1 部署到主網

# 部署到以太坊主網
cd packages/hardhat
npx hardhat run deploy/00_deploy_your_contract.ts --network mainnet

7.2 前端部署

使用 Vercel 或類似平台部署 Next.js 前端:

# 使用 Vercel CLI 部署
npm i -g vercel
vercel

# 或連接到 GitHub 倉庫進行 CI/CD 部署

7.3 合約驗證

部署後在 Etherscan 上驗證合約:

npx hardhat verify --network mainnet CONTRACT_ADDRESS "constructor arguments"

八、效能優化

8.1 減少 Gas 成本

在 Solidity 合約中優化 Gas 消耗:

// 優化前
function processArray(uint256[] calldata arr) public view returns (uint256) {
    uint256 sum = 0;
    for (uint256 i = 0; i < arr.length; i++) {
        sum += arr[i];
    }
    return sum;
}

// 優化後:使用 assembly
function processArrayOptimized(uint256[] calldata arr) public view returns (uint256 sum) {
    assembly {
        for { let i := 0 } lt(i, arr.length) { i := add(i, 1) } {
            sum := add(sum, calldataload(add(arr.offset, mul(i, 0x20))))
        }
    }
}

8.2 前端載入優化

使用 React 懶加載:

import dynamic from "next/dynamic";

const HeavyComponent = dynamic(
  () => import("./HeavyComponent"),
  { loading: () => <p>Loading...</p> }
);

8.3 快取策略

利用 React Query 快取合約數據:

import { useQuery } from "@tanstack/react-query";

const { data } = useQuery({
  queryKey: ["tokenBalance", address],
  queryFn: () => contract.balanceOf(address),
  staleTime: 60000, // 1 分鐘內不重新獲取
});

九、常見問題與解決方案

9.1 合約部署失敗

問題:部署時出現 "insufficient funds" 錯誤

解決方案

  1. 檢查錢包餘額是否充足
  2. 確認 .env 中的私鑰正確
  3. 檢查 RPC URL 是否正確

9.2 前端無法連接錢包

問題:錢包連接按鈕無響應

解決方案

  1. 確保 MetaMask 已安裝
  2. 檢查瀏覽器擴展是否啟用
  3. 清除瀏覽器緩存並重試

9.3 合約方法調用失敗

問題:調用合約時出現 revert

解決方案

  1. 使用 try-catch 捕獲錯誤
  2. 檢查合約函數參數類型
  3. 確認合約已正確部署

9.4 Hardhat 節點連接問題

問題:無法連接到本地 Hardhat 節點

解決方案

  1. 確保 Hardhat 節點正在運行
  2. 檢查 port 8545 是否被佔用
  3. 確認 network 配置正確

十、總結

Scaffold-ETH 為以太坊應用開發提供了完整的解決方案。通過整合 Hardhat、Next.js、Wagmi 等工具,它大幅降低了 Web3 開發的門檻,使開發者能夠專注於業務邏輯而非基礎設施。

掌握 Scaffold-ETH 意味著:

建議開發者從簡單的項目開始,逐步掌握框架的各個方面,然後挑戰更複雜的去中心化應用。隨著經驗積累,你將能夠充分利用 Scaffold-ETH 的靈活性,構建真正創新的區塊鏈應用。

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!