初识Web3之从零构建许愿墙DApp
春节假期闲来无聊,打算了解一下web3,借助AI构建了一个简单的许愿墙 DApp,学习Web2应用和Web3应用有什么差异。
这篇文章主要整理一下整个过程,包括遇到的坑和解决方案。
项目概述
许愿墙 DApp 是一个去中心化应用,用户可以:
- 连接 MetaMask 钱包
- 在区块链上发布愿望
- 查看所有人的愿望
- 愿望永久存储,不可篡改
技术栈:
- 前端:React + Vite + ethers.js
- 智能合约:Solidity
- 开发工具:Remix IDE
- 测试网络:Sepolia
第一步:理解 DApp 与传统应用的区别
在开始动手之前,我们先来看看 DApp 和传统应用到底有什么不同。
传统 Web2 应用架构
用户 → 前端 → 后端服务器 → 数据库(MySQL/MongoDB)- 数据存储在中心化服务器
- 公司可以随意修改/删除数据
- 需要信任运营方
- 服务器宕机则应用不可用
Web3 DApp 架构
用户 → 前端 → 区块链网络(全球分布式节点)- 数据存储在去中心化网络
- 一旦写入,任何人都无法篡改
- 不需要信任中心化机构
- 没有单点故障
- 每次写入需要支付 Gas 费
什么是 ETH?
ETH(以太币)是以太坊区块链的原生加密货币,类似于比特币网络中的 BTC。它主要有两个用途:
- 价值存储和转账:可以像传统货币一样在用户之间转账
- 支付 Gas 费:在以太坊上执行任何操作(比如发布愿望)都需要消耗计算资源,需要用 ETH 支付费用
什么是 Gas 费?
Gas 费是使用以太坊网络的"手续费"。可以这样理解:
- 你的愿望需要被全球成千上万台电脑记录和验证
- 这些电脑(节点)需要消耗电力和计算资源
- Gas 费就是对这些节点运营者的补偿
为什么需要 Gas 费?
- 激励节点运营者:补偿他们维护网络的成本
- 防止垃圾交易:如果免费,恶意用户可能发送大量无意义交易堵塞网络
- 资源分配:Gas 费高的交易会被优先处理
Gas 费的组成:
Gas 费 = Gas Used(消耗量) × Gas Price(单价)
例如:
发布一个愿望消耗 50,000 Gas
Gas Price 是 20 Gwei
总费用 = 50,000 × 20 Gwei = 0.001 ETH ≈ $2-3(主网)在测试网上,ETH 是免费的,所以可以放心实验!
第二步:环境准备
1. 安装 MetaMask
MetaMask 是你的数字钱包,相当于"身份证 + 银行账户"。
安装步骤:
- 访问 https://metamask.io/
- 选择你的浏览器(Chrome/Firefox/Edge)
- 点击"添加到浏览器"
- 创建新钱包,设置密码
- 重要:备份助记词(12 个单词,务必妥善保管,丢了就找不回来了)
什么是钱包?
在 Web3 中,钱包不是存储加密货币的地方,而是管理你的"数字身份"的工具。
钱包 vs 账户:
钱包(Wallet)= 管理工具
├── 账户 1(Account 1): 0x1234...
├── 账户 2(Account 2): 0x5678...
└── 账户 3(Account 3): 0xabcd...- 钱包:像一个"钥匙串",可以管理多个账户(MetaMask 就是一个钱包)
- 账户:一个具体的区块链地址,有自己的余额和交易历史
钱包的核心功能:
管理私钥
- 私钥是一串随机数字,证明你拥有某个账户
- 类似银行账户的密码,但丢失无法找回
- 助记词(12 个单词)可以恢复私钥
签署交易
- 当你发布愿望时,钱包用私钥"签名"
- 签名证明这个操作确实是你本人发起的
- 就像手写签名,但无法伪造
与 DApp 交互
- 连接网站(如我们的许愿墙)
- 授权交易
- 查看余额和历史
关键概念:
公钥/地址(Public Key/Address)
- 类似银行账号:0x1234...5678
- 可以公开,别人可以给你转账
- 显示在区块浏览器上
私钥(Private Key)
- 类似银行密码
- 绝对不能告诉任何人
- 丢失 = 永久失去资产
助记词(Seed Phrase)
- 12 个英文单词
- 可以恢复私钥
- 务必手写备份,不要截图或保存在电脑上(血的教训)重要提醒:
- 你的加密货币不是"存在"钱包里,而是记录在区块链上
- 钱包只是存储私钥,让你能够控制区块链上的资产
- 私钥 = 所有权,谁有私钥谁就拥有资产
2. 切换到测试网络
学习阶段必须使用测试网,避免花费真实的 ETH。
切换步骤:
- 打开 MetaMask
- 点击左上角网络下拉菜单
- 如果看不到测试网:设置 → 高级 → 显示测试网络(开启)
- 选择 "Sepolia 测试网络"
3. 获取测试 ETH
在测试网上需要免费的测试币来支付 Gas 费。
水龙头网站:
- https://cloud.google.com/application/web3/faucet/ethereum/sepolia - Google Cloud 提供,每天免费领取 0.05 ETH,没有任何门槛,强烈推荐
- https://www.alchemy.com/faucets/ethereum-sepolia - Alchemy 提供,需要钱包有最少 0.001 ETH 才能领取
- https://faucet.quicknode.com/ethereum/sepolia - QuickNode 提供,需要 Twitter 账号验证
每次可领取 0.05-0.5 ETH 测试币,足够发布很多愿望。
第三步:编写智能合约
智能合约是运行在区块链上的代码,定义了应用的核心逻辑。我们来看看怎么写一个简单的合约。
智能合约 vs 比特币区块链
比特币和以太坊都是区块链,但功能差异巨大:
比特币区块链(Bitcoin):
功能:转账
A 给 B 转 1 BTC → 记录在区块链上 → 完成
特点:
- 只能转账,不能运行程序
- 脚本语言非常有限
- 像一个"分布式账本"以太坊区块链(Ethereum):
功能:转账 + 运行程序(智能合约)
A 调用合约 → 合约执行逻辑 → 改变状态 → 记录在区块链上
特点:
- 可以运行复杂程序
- 图灵完备的编程语言(Solidity)
- 像一个"世界计算机"形象比喻:
| 比特币 | 以太坊 |
|---|---|
| 计算器 | 电脑 |
| 只能加减乘除 | 可以运行各种程序 |
| 转账功能 | 转账 + DeFi + NFT + 游戏... |
智能合约能做什么?
去中心化金融(DeFi)
- 借贷平台(Aave, Compound)
- 去中心化交易所(Uniswap)
- 稳定币(USDC, DAI)
NFT(非同质化代币)
- 数字艺术品
- 游戏道具
- 域名(ENS)
DAO(去中心化自治组织)
- 链上投票
- 社区治理
- 资金管理
其他应用
- 供应链追踪
- 身份认证
- 预测市场
我们的许愿墙就是一个简单的智能合约应用,它在区块链上存储和管理数据,这在比特币上是没法实现的。
合约代码
Solidity 语言简介
Solidity 是以太坊智能合约的主要编程语言,专门为区块链设计。
语言特点:
类似 JavaScript
- 语法接近 JavaScript/C++
- 容易上手
- 静态类型(需要声明变量类型)
面向合约
- 使用
contract关键字定义合约(类似 class) - 支持继承、接口、库
- 使用
区块链特性
msg.sender:调用者地址block.timestamp:当前区块时间payable:可以接收 ETHview/pure:只读函数,不消耗 Gas
基本语法对比:
// Solidity
contract MyContract {
uint256 public count; // 状态变量,存储在区块链上
function increment() public {
count += 1;
}
}// JavaScript(对比)
class MyContract {
constructor() {
this.count = 0; // 存储在内存中
}
increment() {
this.count += 1;
}
}关键区别:
- Solidity 的状态变量永久存储在区块链上
- 每次修改都需要支付 Gas 费
- 代码一旦部署,没法修改
常用数据类型:
uint256 // 无符号整数(0 到 2^256-1)
address // 以太坊地址(0x1234...)
string // 字符串
bool // 布尔值
mapping // 类似哈希表/字典
array[] // 数组
struct // 结构体OK,现在我们来看看许愿墙的合约代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract WishWall {
struct Wish {
address author;
string content;
uint256 timestamp;
}
Wish[] public wishes;
event WishCreated(address indexed author, string content, uint256 timestamp);
function createWish(string memory _content) public {
require(bytes(_content).length > 0, unicode"愿望不能为空");
require(bytes(_content).length <= 280, unicode"愿望太长了");
wishes.push(Wish({
author: msg.sender,
content: _content,
timestamp: block.timestamp
}));
emit WishCreated(msg.sender, _content, block.timestamp);
}
function getWishCount() public view returns (uint256) {
return wishes.length;
}
function getWish(uint256 _index) public view returns (address, string memory, uint256) {
require(_index < wishes.length, unicode"愿望不存在");
Wish memory wish = wishes[_index];
return (wish.author, wish.content, wish.timestamp);
}
function getAllWishes() public view returns (Wish[] memory) {
return wishes;
}
}关键概念解释
struct(结构体): 定义愿望的数据结构,包含作者、内容、时间戳。
array(数组):wishes[] 存储所有愿望。
event(事件): 记录链上事件,方便前端监听和查询。
require(断言): 输入验证,确保数据合法。
unicode 字符串: Solidity 0.8+ 需要使用 unicode"..." 来支持中文,这里需要注意一下。
第四步:部署智能合约
使用 Remix IDE 在线部署,无需本地环境。
部署步骤
打开 Remix
创建合约文件
- 点击"文件浏览器"
- 创建新文件
WishWall.sol - 粘贴合约代码
编译合约
- 点击"Solidity 编译器"图标
- 选择编译器版本
0.8.0+ - 点击"Compile WishWall.sol"
- 看到绿色对勾表示成功

部署合约
- 点击"部署和运行交易"图标
- ENVIRONMENT 选择 "Injected Provider - MetaMask"
- 确认 MetaMask 连接到 Sepolia 测试网
- 点击 "Deploy" 按钮
- MetaMask 弹出确认,点击"确认"
- 等待交易确认(约 10-30 秒)

复制合约地址
- 部署成功后,在 "Deployed Contracts" 下方会显示合约
- 点击复制按钮,保存合约地址(类似
0x1234...abcd)
常见问题
问题 1:编译错误 "Invalid character in string"
错误:require(bytes(_content).length > 0, "愿望不能为空");
正确:require(bytes(_content).length > 0, unicode"愿望不能为空");中文字符串必须使用 unicode 前缀。
问题 2:MetaMask 连接的是主网
部署前务必确认:
- MetaMask 左上角显示 "Sepolia 测试网络"
- 如果显示 "Ethereum Mainnet",立即切换!
- 主网需要真实 ETH,非常昂贵
第五步:构建前端应用
接下来我们来看看前端部分,这里跟传统 Web 开发还是有些区别的。
Web3 前端 vs 传统 Web 前端
Web3 前端应用和传统 Web 应用在架构上有本质区别:
传统 Web2 前端:
前端(React)
↓ HTTP/REST API
后端服务器(Node.js/Python)
↓ SQL
数据库(MySQL/MongoDB)Web3 DApp 前端:
前端(React)
↓ ethers.js/web3.js
钱包(MetaMask)
↓ JSON-RPC
区块链网络(以太坊节点)主要区别:
| 特性 | 传统 Web2 | Web3 DApp |
|---|---|---|
| 后端 | 自己的服务器 | 区块链网络 |
| 数据库 | MySQL/MongoDB | 智能合约 |
| 身份认证 | 用户名/密码 | 钱包签名 |
| API 调用 | REST/GraphQL | 合约函数调用 |
| 数据读取 | 免费 | 免费 |
| 数据写入 | 免费 | 需要 Gas 费 |
| 响应速度 | 毫秒级 | 秒级(需要确认) |
Web3 前端的核心功能:
连接钱包
javascript// 请求用户授权连接钱包 await window.ethereum.request({ method: 'eth_requestAccounts' })读取区块链数据
javascript// 调用合约的 view 函数(免费) const wishes = await contract.getAllWishes()发送交易
javascript// 调用合约的写入函数(需要 Gas) const tx = await contract.createWish("我的愿望") await tx.wait() // 等待交易确认监听事件
javascript// 监听合约事件 contract.on("WishCreated", (author, content) => { console.log(`${author} 发布了愿望: ${content}`) })
Web3 前端的特殊之处:
- 无需后端服务器:智能合约就是后端
- 用户自己支付费用:每次写入操作用户支付 Gas
- 数据公开透明:任何人都能读取区块链数据
- 异步确认:交易需要等待 10-30 秒确认
- 不可撤销:交易一旦确认,没法回滚
开发体验对比:
// 传统 Web2
async function createPost(content) {
const response = await fetch('/api/posts', {
method: 'POST',
headers: { 'Authorization': 'Bearer token' },
body: JSON.stringify({ content })
})
return response.json() // 立即返回
}
// Web3 DApp
async function createWish(content) {
const tx = await contract.createWish(content) // 发送交易
await tx.wait() // 等待 10-30 秒确认
// 交易已永久记录在区块链上
}OK,现在我们来看看如何构建 Web3 前端:
项目结构
wish-wall-dapp/
├── src/
│ ├── App.jsx # 主应用组件
│ ├── App.css # 样式文件
│ ├── main.jsx # 入口文件
│ └── index.css # 全局样式
├── contracts/
│ └── WishWall.sol # 智能合约
├── index.html
├── package.json
└── vite.config.js核心代码
import { useState } from 'react'
import { ethers } from 'ethers'
import './App.css'
// 合约 ABI
const CONTRACT_ABI = [
"function createWish(string _content) public",
"function getWishCount() public view returns (uint256)",
"function getWish(uint256 _index) public view returns (address, string, uint256)",
"function getAllWishes() public view returns (tuple(address author, string content, uint256 timestamp)[])",
"event WishCreated(address indexed author, string content, uint256 timestamp)"
]
// 替换为你的合约地址
const CONTRACT_ADDRESS = "0xYourContractAddress"
function App() {
const [account, setAccount] = useState('')
const [contract, setContract] = useState(null)
const [wishes, setWishes] = useState([])
const [newWish, setNewWish] = useState('')
const [loading, setLoading] = useState(false)
// 连接钱包
const connectWallet = async () => {
try {
if (!window.ethereum) {
alert('请安装 MetaMask!')
return
}
// 检查并切换到 Sepolia 测试网
const chainId = await window.ethereum.request({ method: 'eth_chainId' })
const sepoliaChainId = '0xaa36a7'
if (chainId !== sepoliaChainId) {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: sepoliaChainId }],
})
} catch (switchError) {
if (switchError.code === 4902) {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: sepoliaChainId,
chainName: 'Sepolia 测试网络',
nativeCurrency: {
name: 'SepoliaETH',
symbol: 'ETH',
decimals: 18
},
rpcUrls: ['https://rpc.sepolia.org'],
blockExplorerUrls: ['https://sepolia.etherscan.io']
}]
})
} else {
alert('请手动切换到 Sepolia 测试网络')
return
}
}
}
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
})
setAccount(accounts[0])
const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const wishWallContract = new ethers.Contract(
CONTRACT_ADDRESS,
CONTRACT_ABI,
signer
)
setContract(wishWallContract)
loadWishes(wishWallContract)
} catch (error) {
console.error('连接钱包失败:', error)
alert('连接失败: ' + error.message)
}
}
// 加载所有愿望
const loadWishes = async (contractInstance) => {
try {
const allWishes = await contractInstance.getAllWishes()
setWishes(allWishes.map(wish => ({
author: wish.author,
content: wish.content,
timestamp: Number(wish.timestamp)
})).reverse())
} catch (error) {
console.error('加载愿望失败:', error)
}
}
// 创建新愿望
const createWish = async () => {
if (!newWish.trim()) {
alert('请输入你的愿望')
return
}
if (!contract) {
alert('请先连接钱包')
return
}
try {
setLoading(true)
console.log('准备发送交易...')
const tx = await contract.createWish(newWish, {
gasLimit: 300000
})
console.log('交易已发送,哈希:', tx.hash)
alert('交易已发送,等待确认...')
const receipt = await tx.wait()
console.log('交易已确认,Gas 使用:', receipt.gasUsed.toString())
setNewWish('')
await loadWishes(contract)
alert('愿望发布成功!')
} catch (error) {
console.error('完整错误:', error)
let errorMsg = '发布失败: '
if (error.reason) {
errorMsg += error.reason
} else if (error.message) {
errorMsg += error.message
} else {
errorMsg += '未知错误'
}
alert(errorMsg)
} finally {
setLoading(false)
}
}
// 格式化地址
const formatAddress = (address) => {
return `${address.slice(0, 6)}...${address.slice(-4)}`
}
// 格式化时间
const formatTime = (timestamp) => {
return new Date(timestamp * 1000).toLocaleString('zh-CN')
}
return (
<div className="app">
<header className="header">
<h1>🌟 许愿墙 DApp</h1>
<p className="subtitle">在区块链上许下你的愿望</p>
{!account ? (
<button onClick={connectWallet} className="connect-btn">
连接钱包
</button>
) : (
<div className="account-info">
已连接: {formatAddress(account)}
</div>
)}
</header>
{account && (
<div className="wish-form">
<textarea
value={newWish}
onChange={(e) => setNewWish(e.target.value)}
placeholder="写下你的愿望..."
maxLength={280}
rows={4}
/>
<div className="form-footer">
<span className="char-count">{newWish.length}/280</span>
<button
onClick={createWish}
disabled={loading}
className="submit-btn"
>
{loading ? '发布中...' : '发布愿望'}
</button>
</div>
</div>
)}
<div className="wishes-container">
<h2>愿望墙 ({wishes.length})</h2>
{wishes.length === 0 ? (
<p className="empty-message">还没有愿望,快来许下第一个愿望吧!</p>
) : (
<div className="wishes-grid">
{wishes.map((wish, index) => (
<div key={index} className="wish-card">
<p className="wish-content">{wish.content}</p>
<div className="wish-meta">
<span className="wish-author">
{formatAddress(wish.author)}
</span>
<span className="wish-time">
{formatTime(wish.timestamp)}
</span>
</div>
</div>
))}
</div>
)}
</div>
</div>
)
}
export default App关键实现
1. 自动切换网络
代码会自动检测当前网络,如果不是 Sepolia,会提示用户切换。
2. Gas 限制
设置 gasLimit: 300000 确保交易有足够的 Gas。
3. 错误处理
详细的错误信息帮助调试问题。
4. 交易确认
使用 tx.wait() 等待交易被打包进区块。
第六步:运行项目
安装依赖
npm install配置合约地址
在 src/App.jsx 中,将 CONTRACT_ADDRESS 替换为你部署的合约地址:
const CONTRACT_ADDRESS = "0xYourContractAddress"启动开发服务器
npm run dev使用流程
- 点击"连接钱包"
- MetaMask 弹出,确认连接
- 自动切换到 Sepolia 测试网(如果需要)
- 输入愿望内容
- 点击"发布愿望"
- MetaMask 弹出交易确认,查看 Gas 费用
- 点击"确认"
- 等待 10-30 秒交易确认
- 愿望显示在墙上
完整效果
最终界面效果如下所示:

在这个过程中,可以深刻体会到 Web3 应用与传统应用的本质区别。
1. 数据存储方式的革命
- 传统应用:数据存储在公司的数据库服务器中,公司可以随意修改或删除
- Web3 应用:数据永久存储在区块链上,一旦发布就没法被任何人(包括开发者)修改或删除
- 实际体验:你发布的每一个愿望都会被全球成千上万个节点记录,即使这个网站关闭了,你的愿望依然存在于区块链上
2. 用户身份系统的变革
- 传统应用:需要注册账号、设置密码、绑定邮箱/手机,用户数据存储在公司服务器
- Web3 应用:通过钱包地址作为唯一身份标识,无需注册,连接钱包即可使用
- 实际体验:你的身份(钱包地址)是全球通用的,可以在任何 DApp 中使用同一个身份
3. 信任模型的转变
- 传统应用:需要信任运营公司不会作恶、不会泄露数据、不会随意修改规则
- Web3 应用:不需要信任任何中心化机构,智能合约代码公开透明,规则由代码保证
- 实际体验:你可以在 Etherscan 上查看合约代码,验证它确实按照承诺的方式运行
4. 成本和激励机制
- 传统应用:用户免费使用,公司承担服务器成本,通过广告或增值服务盈利
- Web3 应用:用户为每次写入操作支付 Gas 费,直接补偿网络维护者
- 实际体验:发布愿望需要支付少量 ETH,但这保证了网络的可持续运行
5. 数据所有权
- 传统应用:你创建的内容属于平台,平台可以随时删除或限制访问
- Web3 应用:你创建的内容永久属于你,任何人都没法剥夺
- 实际体验:你的愿望永远与你的钱包地址关联,这是不可篡改的所有权证明
6. 应用的持久性
- 传统应用:如果公司倒闭或服务器关闭,应用和数据都会消失
- Web3 应用:只要区块链网络存在,应用就能继续运行,前端可以托管在任何地方
- 实际体验:即使原开发者停止维护,任何人都可以部署新的前端继续访问合约
7. 透明度和可验证性
- 传统应用:后端逻辑是黑盒,用户没法验证系统是否按承诺运行
- Web3 应用:智能合约代码完全公开,所有交易记录可追溯
- 实际体验:你可以在区块浏览器上查看每一笔交易的详细信息,包括 Gas 费用、执行结果等
这些特性共同构成了 Web3 的核心价值:去中心化、透明、不可篡改、用户拥有数据所有权。虽然目前 Web3 还有性能、成本、用户体验等方面的挑战,但它代表了互联网发展的一个重要方向。
前端主要是做查询、展示和提交,跟传统的 web 前端开发差别不太大。
调试问题记录
在开发过程中踩了一些坑,这里记录一下。
问题 1:交易失败 - 销毁地址警告
现象:
您正在将资产发送至销毁地址。若继续操作,您将损失该笔资产。原因: 合约地址还是默认的 0x0000000000000000000000000000000000000000(零地址)。
解决: 在 Remix 中部署合约,复制真实的合约地址,替换代码中的 CONTRACT_ADDRESS。
问题 2:Gas 费用异常高(0.09 ETH)
现象: 发布愿望时,Gas 费用显示 0.09 ETH,远超测试币余额。
原因: MetaMask 连接到了以太坊主网,而不是 Sepolia 测试网。
解决:
- 打开 MetaMask
- 点击左上角网络名称
- 切换到 "Sepolia 测试网络"
- 刷新页面,重新连接
正常情况下,Sepolia 的 Gas 费用应该是 0.0001-0.003 ETH。
问题 3:Remix 中测试合约失败
现象: 在 Remix 的 "Low level interactions" 区域看到错误:
Both 'receive' and 'fallback' functions are not defined原因: 这个警告是因为合约不能接收 ETH,但这不是问题,因为我们的合约本来就不需要接收 ETH。
解决: 忽略这个警告,直接点击橙色的 createWish 按钮测试功能就行。
问题 4:交易发送后失败
调试步骤:
- 打开浏览器控制台(F12)
- 查看详细错误信息
- 增加 Gas 限制到 300000
- 确认合约地址正确
- 在 Remix 中测试合约函数是否正常
Web3 核心概念深入理解
区块链 vs Git
区块链和 Git 有很多相似之处:
相似点:
- 分布式存储(每个节点/开发者都有完整副本)
- 不可篡改的历史(每个区块/commit 有唯一 hash)
- 透明可见(所有记录公开)
- 防止单点篡改(修改历史会被发现)
关键区别:
| 特性 | Git | 区块链 |
|---|---|---|
| 权限模型 | 有管理员 | 无权限,任何人可提交 |
| 同步机制 | 手动 pull | 自动实时同步 |
| 冲突解决 | 手动解决 | 算法自动解决(共识) |
| 验证机制 | 信任为主 | 零信任,全部验证 |
| 共识机制 | 无 | 强制共识(PoW/PoS) |
| 成本 | 免费 | 付费(Gas) |
本质理解:
区块链 = Git + 自动同步 + 共识机制 + 经济激励 - 中心化权限
主网的本质
主网不是部署在某个服务器上,而是由全球成千上万台电脑(节点)组成的网络。
结构:
🖥️ 美国节点 (~2000)
🖥️ 欧洲节点 (~1500)
🖥️ 亚洲节点 (~1000)
↓
所有节点存储完整区块链
通过 P2P 网络同步不同的区块链 = 不同的主网:
- 以太坊主网(Chain ID: 1)
- Polygon 主网(Chain ID: 137)
- BNB Chain(Chain ID: 56)
- Arbitrum(Chain ID: 42161)
每个主网都有对应的测试网用于开发。
Gas 费用详解
为什么需要 Gas 费?
- 激励矿工/验证者处理交易
- 防止垃圾交易(spam)
- 你的每个愿望都需要全球成千上万台电脑记录
Gas 费计算:
Gas 费 = Gas Used × Gas Price
例如:
Gas Used: 50,000(发布愿望消耗的计算量)
Gas Price: 20 Gwei(你愿意支付的单价)
Gas 费 = 50,000 × 20 = 1,000,000 Gwei = 0.001 ETH单位换算:
1 ETH = 1,000,000,000 Gwei (10^9)
1 Gwei = 0.000000001 ETH交易生命周期
1. 发起交易
↓
2. MetaMask 签名(证明是你本人)
↓
3. 广播到网络(发送给多个节点)
↓
4. 进入交易池(等待打包)
↓
5. 验证者选择交易打包进区块
↓
6. 区块广播到全网
↓
7. 所有节点验证并接受
↓
8. 交易确认完成整个过程大约 10-30 秒。
智能合约的不可变性与升级策略
一旦部署,无法修改:
- 代码永久存在链上
- 无法删除或更新
- 这是特性,不是 bug(保证了去中心化和可信任性)
这是智能合约最重要的特性之一,但也带来了挑战:如果发现 bug 或需要添加新功能怎么办?
方案 1:部署新合约(最简单)
适用场景:
- 合约逻辑简单
- 历史数据不重要
- 用户量较小
操作步骤:
- 修改合约代码
// 新版本:添加点赞功能
contract WishWallV2 {
struct Wish {
address author;
string content;
uint256 timestamp;
uint256 likes; // 新增
}
// ... 其他代码
function likeWish(uint256 _index) public {
wishes[_index].likes++;
}
}在 Remix 中部署新合约
- 编译新版本合约
- 部署到区块链
- 获得新的合约地址
更新前端代码
// 旧地址
const OLD_CONTRACT_ADDRESS = "0x旧合约地址"
// 新地址
const CONTRACT_ADDRESS = "0x新合约地址"
// 更新 ABI(添加新函数)
const CONTRACT_ABI = [
"function createWish(string _content) public",
"function getAllWishes() public view returns (...)",
"function likeWish(uint256 _index) public" // 新增
]- 通知用户
- 发布公告说明升级
- 旧合约数据仍然存在,但不再使用
优点:
- 简单直接
- 完全控制新合约
缺点:
- 旧数据丢失(除非手动迁移)
- 用户需要重新授权
- 合约地址改变
方案 2:数据迁移(保留历史)
如果需要保留旧数据,可以在新合约中读取旧合约的数据。
实现方式:
// 新合约引用旧合约
contract WishWallV2 {
WishWall public oldContract; // 旧合约实例
constructor(address _oldContractAddress) {
oldContract = WishWall(_oldContractAddress);
}
// 获取所有愿望(包括旧数据)
function getAllWishes() public view returns (Wish[] memory) {
// 先获取旧合约的数据
Wish[] memory oldWishes = oldContract.getAllWishes();
// 合并新旧数据
Wish[] memory allWishes = new Wish[](oldWishes.length + wishes.length);
for (uint i = 0; i < oldWishes.length; i++) {
allWishes[i] = oldWishes[i];
}
for (uint i = 0; i < wishes.length; i++) {
allWishes[oldWishes.length + i] = wishes[i];
}
return allWishes;
}
}优点:
- 保留历史数据
- 用户体验连续
缺点:
- 读取旧数据会消耗更多 Gas
- 实现复杂度增加
方案 3:代理模式(Proxy Pattern)- 专业方案
代理模式是最灵活的升级方案,被 DeFi 项目广泛使用。
核心思想:
用户 → 代理合约(地址不变)→ 逻辑合约(可升级)架构:
// 代理合约(地址永不改变)
contract Proxy {
address public implementation; // 指向逻辑合约
// 将所有调用转发到逻辑合约
fallback() external payable {
address impl = implementation;
assembly {
// 转发调用
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
// 升级函数(只有管理员可调用)
function upgradeTo(address newImplementation) external {
require(msg.sender == admin, "Only admin");
implementation = newImplementation;
}
}
// 逻辑合约 V1
contract WishWallV1 {
// 业务逻辑
}
// 逻辑合约 V2(升级版)
contract WishWallV2 {
// 新的业务逻辑
}升级流程:
- 部署代理合约(一次性)
- 部署逻辑合约 V1
- 代理合约指向 V1
- 用户使用代理合约地址
- 需要升级时:
- 部署逻辑合约 V2
- 调用代理合约的
upgradeTo(V2地址) - 用户无感知,继续使用相同地址
优点:
- 合约地址永不改变
- 可以无限次升级
- 数据保留在代理合约中
- 用户体验最好
缺点:
- 实现复杂
- 需要仔细设计存储布局
- 有安全风险(管理员权限过大)
使用 OpenZeppelin 的代理合约:
npm install @openzeppelin/contracts-upgradeable// 使用 OpenZeppelin 的可升级合约
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract WishWall is Initializable {
function initialize() public initializer {
// 初始化逻辑(替代 constructor)
}
}方案 4:预留升级接口(设计时考虑)
在设计合约时就考虑未来的扩展性。
示例:
contract WishWall {
address public admin;
bool public paused;
// 暂停功能(发现 bug 时紧急暂停)
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
function pause() external {
require(msg.sender == admin, "Only admin");
paused = true;
}
function unpause() external {
require(msg.sender == admin, "Only admin");
paused = false;
}
// 所有写入函数都添加 whenNotPaused
function createWish(string memory _content) public whenNotPaused {
// ...
}
// 预留扩展接口
mapping(uint256 => bytes) public extensionData;
function setExtensionData(uint256 _wishId, bytes memory _data) public {
// 未来可以用来存储额外数据
extensionData[_wishId] = _data;
}
}实际项目中的建议
对于学习项目(如许愿墙):
- 使用方案 1(部署新合约)
- 简单直接,易于理解
对于小型项目:
- 使用方案 2(数据迁移)
- 保留用户数据,体验更好
对于大型 DeFi 项目:
- 使用方案 3(代理模式)
- 使用 OpenZeppelin 的成熟方案
- 进行专业的安全审计
最佳实践:
- 充分测试:在测试网上反复测试,确保没有 bug
- 代码审计:重要项目必须进行安全审计
- 时间锁:升级操作设置延迟期,给用户反应时间
- 多签钱包:升级权限由多人共同控制
- 逐步去中心化:最终将升级权限交给社区治理
我们的许愿墙升级示例
假设我们要添加点赞功能,使用最简单的方案:
- 修改合约(已在"项目扩展建议"章节展示)
- 部署新合约到 Sepolia
- 更新前端:
const CONTRACT_ADDRESS = "0x新合约地址"
const CONTRACT_ABI = [
// ... 原有函数
"function likeWish(uint256 _index) public",
"function getWishLikes(uint256 _index) public view returns (uint256)"
]- 添加点赞按钮到前端 UI
- 发布更新
这就是最简单的合约升级流程!
读操作 vs 写操作
读操作(免费):
await contract.getAllWishes() // 查看愿望
await contract.getWishCount() // 获取数量- 不改变区块链状态
- 不需要 Gas 费
- 瞬间完成
写操作(需要 Gas):
await contract.createWish("我的愿望") // 发布愿望- 改变区块链状态
- 需要支付 Gas 费
- 需要等待确认
Web3 的成本与适用场景
这是理解 Web3 应用的关键问题:Web3 的成本结构与传统 Web2 完全不同,这直接决定了它的适用场景。
成本对比分析
传统 Web2 应用:
用户操作:完全免费
公司成本(每月):
- 服务器:$100-1,000
- 数据库:$50-500
- 带宽:$50-1,000
- 运维人员:$5,000-20,000
商业模式:公司承担成本,通过广告/会员盈利
用户体验:免费使用,无感知Web3 应用(以太坊主网):
用户操作:每次写入需要 Gas 费
- 发布一条消息:$5-50
- 简单转账:$2-20
- 复杂合约交互:$20-200
- 网络拥堵时可能更贵
商业模式:用户直接承担成本
用户体验:每次操作都需要支付并等待确认为什么 Web3 成本这么高?
全球冗余存储:
Web2:数据存储 1-3 个副本(主库 + 备份)
Web3:数据存储在数千个节点上
你的一条愿望 = 全球数千台电脑都要存储和验证共识机制成本:
Web2:中心化验证,毫秒级完成
Web3:需要多个节点达成共识,消耗大量计算资源永久存储成本:
Web2:可以删除旧数据,降低成本
Web3:数据永久存储,存储成本持续累积Web3 的适用场景
✅ 适合 Web3 的场景
金融应用(DeFi)
典型应用:Uniswap(去中心化交易所)、Aave(借贷协议)
为什么适合:
- 涉及真实价值转移
- 需要绝对的透明和不可篡改
- Gas 费相对交易金额很小
实际案例:
- 交易 $10,000,Gas 费 $20 = 0.2%
- 用户可以接受这个成本
- 去中心化带来的价值 > 成本数字资产(NFT)
典型应用:OpenSea(NFT 市场)、艺术品、游戏道具
为什么适合:
- 需要证明所有权和稀缺性
- 一次性铸造,长期持有
- 资产价值远大于 Gas 费
实际案例:
- 购买一个 $1,000 的 NFT
- Gas 费 $50 = 5%
- 但获得了永久的所有权证明身份和凭证
典型应用:ENS 域名(vitalik.eth)、链上学历证书
为什么适合:
- 需要永久存在
- 防伪造
- 低频操作(一次性或很少修改)
实际案例:
- 注册一个 ENS 域名,支付 $50
- 终身有效,可以转让
- 一次性成本,长期价值治理和投票
典型应用:DAO 治理、社区决策
为什么适合:
- 需要透明和防篡改
- 投票结果需要永久记录
- 低频高价值操作
实际案例:
- MakerDAO 治理投票
- 每次投票 $10-50
- 但决定数百万美元的资金使用供应链溯源
典型应用:奢侈品防伪、食品安全追踪
为什么适合:
- 需要不可篡改的记录
- 商品价值远大于 Gas 费
- 低频写入(生产、流转节点)
实际案例:
- 一瓶 $1,000 的红酒
- 溯源记录成本 $5
- 提升了商品可信度和价值❌ 不适合 Web3 的场景
社交媒体
场景:Twitter、微博、朋友圈
为什么不适合:
- 高频操作(每天几十次发布、点赞、评论)
- 每次 $5-50 的 Gas 费用户无法接受
- 内容价值低,不值得上链
如果 Twitter 上链:
- 发一条推文:$10
- 点赞:$5
- 转发:$10
- 每天成本:$100+
→ 完全不可行即时通讯
场景:微信、WhatsApp、Telegram
为什么不适合:
- 超高频(每天数百条消息)
- 需要实时性(毫秒级响应)
- 区块链确认太慢(10-30 秒)
实际体验:
- 发一条消息 $5 + 等待 30 秒
- 用户体验灾难视频/图片存储
场景:YouTube、Instagram、抖音
为什么不适合:
- 数据量巨大(GB 级别)
- 链上存储成本天文数字
实际成本:
- 存储 1GB 数据到以太坊:数百万美元
- 完全不现实
- 即使用 IPFS,成本也很高游戏实时数据
场景:游戏中的移动、攻击、技能释放
为什么不适合:
- 超高频操作(每秒数十次)
- 需要毫秒级响应
- 区块链无法满足性能要求
实际体验:
- 每次移动 $5 + 等待 30 秒
- 游戏完全无法玩搜索引擎
场景:Google、百度
为什么不适合:
- 需要中心化的索引和排序算法
- 实时性要求极高
- 数据量巨大
- 区块链无法提供高效的搜索功能混合架构:Web2 + Web3
实际上,大多数成功的 Web3 应用都采用混合架构,结合两者的优势:
架构设计:
链上(Web3)存储:
✓ 资产所有权(NFT、代币)
✓ 关键交易记录
✓ 治理决策
✓ 智能合约逻辑
链下(Web2)存储:
✓ 用户界面和前端
✓ 社交互动(评论、点赞)
✓ 大文件存储(图片、视频 → IPFS)
✓ 实时数据和缓存
✓ 搜索和筛选功能成功案例:OpenSea(NFT 市场)
链上:
- NFT 所有权记录
- 交易历史
- 智能合约(买卖逻辑)
链下:
- NFT 图片存储(IPFS)
- 搜索和筛选功能
- 用户评论和收藏
- 实时价格更新
- 推荐算法成功案例:Axie Infinity(区块链游戏)
链上:
- 游戏资产(Axie NFT)
- 代币经济(SLP、AXS)
- 资产交易
链下:
- 游戏战斗逻辑
- 实时对战
- 游戏画面渲染
- 社交功能Layer 2 解决方案
为了降低成本和提高性能,出现了多层网络架构:
以太坊主网(Layer 1):
Gas 费:$5-50
速度:15 TPS(每秒交易数)
安全性:最高
适用:高价值资产和交易Layer 2(Arbitrum, Optimism, zkSync):
Gas 费:$0.1-1(降低 90%+)
速度:1,000+ TPS
安全性:继承主网安全性
适用:日常交易和应用侧链(Polygon, BNB Chain):
Gas 费:$0.01-0.1(降低 99%+)
速度:10,000+ TPS
安全性:独立的安全机制
适用:高频低价值操作成本对比:
发布一个愿望的成本:
- 以太坊主网:$10-50
- Arbitrum (L2):$0.5-2
- Polygon:$0.01-0.1
同样的功能,成本差异 100-1000 倍!未来趋势
成本会持续降低:
- Layer 2 技术日益成熟
- 以太坊升级(Danksharding)
- 更高效的共识机制(PoS)
- 数据可用性层(DA Layer)
应用会分层部署:
高价值资产 → 以太坊主网(最安全,最贵)
中等价值 → Layer 2(平衡安全性和成本)
低价值高频 → 侧链/其他链(最便宜,最快)混合架构成为主流:
- 关键数据上链(所有权、交易)
- 非关键数据链下(内容、社交)
- 用户体验接近 Web2
- 保留 Web3 的核心价值
选择 Web3 的决策标准
使用 Web3 当满足以下条件:
✓ 价值 > 成本(资产价值远大于 Gas 费)
✓ 需要去中心化(抗审查、无单点故障)
✓ 需要不可篡改(永久记录、可验证)
✓ 低频高价值操作(不是每秒都要交互)
✓ 需要透明公开(所有人可验证)
✓ 需要用户拥有数据所有权使用 Web2 当满足以下条件:
✓ 高频低价值操作(社交、聊天)
✓ 需要实时性(毫秒级响应)
✓ 大数据量(视频、图片)
✓ 用户体验优先(免费、快速)
✓ 需要复杂的查询和搜索
✓ 需要中心化的内容审核关键洞察
Web3 不是要替代 Web2,而是在特定场景下提供更好的解决方案。就像:
- 飞机不会替代汽车(各有适用场景)
- 区块链不会替代数据库(各有优势)
- 去中心化不会替代中心化(各有价值)
我们的许愿墙项目是一个教学案例,帮助理解 Web3 的基本原理。在实际应用中,类似的社交功能更适合采用混合架构:
- 愿望的所有权记录上链(证明是你发布的)
- 愿望内容存储在 IPFS 或中心化服务器(降低成本)
- 点赞、评论等社交互动在链下(提升体验)
Web3 的真正价值在于它为特定问题提供了更好的解决方案:
- 金融:无需信任的价值转移
- 资产:可验证的数字所有权
- 治理:透明的决策机制
- 身份:用户控制的数字身份
理解了成本和适用场景,你就能更好地判断何时使用 Web3,何时使用 Web2,以及如何结合两者的优势。
项目扩展建议
学完基础后,可以尝试添加以下功能:
1. 点赞功能
mapping(uint256 => uint256) public wishLikes;
mapping(uint256 => mapping(address => bool)) public hasLiked;
function likeWish(uint256 _index) public {
require(!hasLiked[_index][msg.sender], "已经点过赞了");
wishLikes[_index]++;
hasLiked[_index][msg.sender] = true;
}2. 愿望分类
enum Category { CAREER, LOVE, HEALTH, WEALTH, OTHER }
struct Wish {
address author;
string content;
uint256 timestamp;
Category category; // 新增
}3. 用户个人主页
显示某个地址发布的所有愿望。
4. 集成 IPFS
将愿望内容存储到 IPFS,链上只存储哈希,节省 Gas。
5. 添加 ENS 支持
显示用户的 ENS 域名(如 vitalik.eth)而不是地址。
学习资源推荐
官方文档:
开发工具:
- Remix IDE - 在线 Solidity IDE
- Hardhat - 本地开发框架
- OpenZeppelin - 安全的合约库
区块浏览器:
- Etherscan - 以太坊主网
- Sepolia Etherscan - Sepolia 测试网
学习平台:
- CryptoZombies - 游戏化学习 Solidity
- Ethereum.org - 官方开发者文档
总结
通过构建这个简单的许愿墙 DApp,了解了:
- Web3 的核心思想:去中心化、不可篡改、透明公开
- 智能合约开发:Solidity 基础语法和最佳实践
- 前端集成:使用 ethers.js 与区块链交互
- 钱包使用:MetaMask 的配置和使用
- 调试技巧:如何排查和解决常见问题
目前看起来,区块链的本质就像一个去中心化的 Git,通过共识机制和经济激励,实现了无需信任的价值传递。
并不是所有的应用都适合放在区块链上面,具体的应用场景,还需要了解一下。
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。
