前端开发者的 Web3 全图解实战 二

⚙️ 第 4 章:使用 ethers.js 与智能合约交互


🧩 4.1 前端和合约是如何"对话"的?

想象你在写一个普通网站时:

  • 你用 axios 请求 REST API;
  • API 返回 JSON 数据。

而在 Web3 世界:

  • "服务器" = 智能合约;
  • "axios" = ethers.js;
  • "接口调用" = 发送交易到区块链。

🧠 类比图

css 复制代码
graph LR
A[React 前端] -->|调用| B[ethers.js]
B -->|发送交易| C[区块链节点 Provider]
C -->|执行| D[智能合约 Contract]
D -->|返回结果| A

🧰 4.2 什么是 ABI?

ABI(Application Binary Interface) 就是合约的"接口说明书"。

例如,你部署了一个合约:

csharp 复制代码
contract Counter {
    uint256 public count;

    function increment() public {
        count += 1;
    }

    function getCount() public view returns(uint256) {
        return count;
    }
}

编译后会生成一份 ABI 文件,告诉前端:

css 复制代码
[  { "inputs": [], "name": "increment", "outputs": [], "stateMutability": "nonpayable", "type": "function" },
  { "inputs": [], "name": "getCount", "outputs": [{ "type": "uint256" }], "stateMutability": "view", "type": "function" }
]

这相当于告诉前端:

「我有两个函数,一个能+1,一个能读取 count。」


🧱 4.3 构建 ethers.js 合约对象

ini 复制代码
import { ethers } from "ethers";

const contractAddress = "0x1234..."; // 你的合约地址
const abi = [ ... ]; // 上面的 ABI

const provider = new ethers.BrowserProvider(window.ethereum);
const contract = new ethers.Contract(contractAddress, abi, provider);

这一步等价于:

"我拿到了一张通往智能合约的地图。"


📖 4.4 调用只读函数(read)

vbscript 复制代码
const count = await contract.getCount();
console.log("当前计数:", count.toString());
  • 不需要钱包签名;
  • 不需要支付 Gas;
  • 只查询链上数据。

✍️ 4.5 调用写入函数(write)

ini 复制代码
const signer = await provider.getSigner();
const contractWithSigner = contract.connect(signer);

const tx = await contractWithSigner.increment();
console.log("交易已发出:", tx.hash);

await tx.wait(); // 等待上链
console.log("✅ 交易确认成功");

📘 写入函数与传统 API 的不同:

  • 必须用钱包签名;
  • 每笔交易都要支付 Gas;
  • 交易需要等待确认(几秒到几十秒)。

⚙️ 交易流程图

rust 复制代码
sequenceDiagram
用户 ->> 前端: 点击"+1 按钮"
前端 ->> MetaMask: 请求签名
MetaMask ->> 用户: 弹出确认窗口
用户 ->> MetaMask: 同意
MetaMask ->> 区块链: 发送交易
区块链 ->> 前端: 返回交易哈希
前端 ->> 用户: 显示"等待确认中"
区块链 ->> 前端: 确认交易成功

🧠 4.6 监听事件(Event)

智能合约可以发出事件,前端可以实时监听。

csharp 复制代码
event Incremented(uint256 newCount);

function increment() public {
    count += 1;
    emit Incremented(count);
}

前端监听:

vbscript 复制代码
contract.on("Incremented", (newCount) => {
  console.log("计数已增加到:", newCount.toString());
});

这就像监听数据库"更新事件"。


✅ 4.7 小结

操作类型 是否签名 是否消耗Gas 示例
read-only ❌ 否 ❌ 否 getCount()
write ✅ 是 ✅ 是 increment()

💰 第 5 章:钱包签名与交易安全


🔐 5.1 钱包签名是什么?

签名(Signature)是用私钥生成的"加密证明"。

📘 类比理解:

你给别人发一封电子信(交易),钱包会帮你签上"数字签名",

任何人都能验证签名真伪,但没人能伪造。


🧩 签名原理图

css 复制代码
graph LR
A[私钥] -->|签名| B[消息]
B -->|生成| C[数字签名]
C -->|验证| D[公钥地址]

✉️ 5.2 签名消息(前端登录)

登录示例(前端签名验证身份):

ini 复制代码
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

const message = "登录验证:" + new Date().toISOString();
const signature = await signer.signMessage(message);
console.log("签名结果:", signature);

这类签名常用于 DApp 登录(例如 OpenSea、Lens)。


🧱 5.3 验证签名(后端或链上)

ini 复制代码
const recoveredAddress = ethers.verifyMessage(message, signature);
console.log("签名者:", recoveredAddress);

如果 recoveredAddress === 用户钱包地址,说明签名有效。


🔄 5.4 交易生命周期(完整可视化)

rust 复制代码
sequenceDiagram
用户 ->> 前端: 点击按钮(例如转账)
前端 ->> 钱包: 构建交易请求
钱包 ->> 用户: 弹出确认窗口
用户 ->> 钱包: 确认
钱包 ->> 区块链: 广播交易
区块链 ->> 矿工: 验证 & 打包
矿工 ->> 区块链: 写入区块
区块链 ->> 前端: 返回交易哈希
前端 ->> 用户: 显示"成功"

⚠️ 5.5 常见错误与解决方案

错误信息 原因 解决
"insufficient funds" 钱包内无 ETH 去测试网水龙头领取
"user rejected transaction" 用户取消签名 引导重新确认
"gas estimation failed" 调用失败 检查函数参数或权限

🧠 第 6 章:构建一个完整的前端 DApp(计数器)


🧩 6.1 我们要实现的功能

  • 连接钱包
  • 显示当前计数
  • 点击按钮自动 +1
  • 实时更新界面

🖼️ 效果示意图

css 复制代码
graph TD
A[按钮:+1] --> B[调用 increment()]
B --> C[MetaMask 确认]
C --> D[交易上链]
D --> E[链上数据更新]
E --> F[前端实时显示 count]

💻 6.2 React 前端完整代码

javascript 复制代码
import { useState, useEffect } from "react";
import { ethers } from "ethers";

const ABI = [
  "function getCount() view returns(uint256)",
  "function increment()"
];
const CONTRACT_ADDRESS = "0xYourContractAddress";

export default function CounterApp() {
  const [count, setCount] = useState(null);
  const [account, setAccount] = useState(null);
  const [loading, setLoading] = useState(false);

  async function connectWallet() {
    const provider = new ethers.BrowserProvider(window.ethereum);
    const [addr] = await provider.send("eth_requestAccounts", []);
    setAccount(addr);
    await fetchCount();
  }

  async function fetchCount() {
    const provider = new ethers.BrowserProvider(window.ethereum);
    const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, provider);
    const value = await contract.getCount();
    setCount(Number(value));
  }

  async function increment() {
    setLoading(true);
    const provider = new ethers.BrowserProvider(window.ethereum);
    const signer = await provider.getSigner();
    const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, signer);
    const tx = await contract.increment();
    await tx.wait();
    await fetchCount();
    setLoading(false);
  }

  return (
    <div style={{ padding: 40 }}>
      <h2>🧮 Web3 计数器 DApp</h2>
      {!account && <button onClick={connectWallet}>连接钱包</button>}
      {account && (
        <>
          <p>当前账户:{account}</p>
          <p>当前计数:{count}</p>
          <button onClick={increment} disabled={loading}>
            {loading ? "交易进行中..." : "+1"}
          </button>
        </>
      )}
    </div>
  );
}

🧠 6.3 合约代码(Solidity)

csharp 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Counter {
    uint256 public count;

    function increment() public {
        count += 1;
    }

    function getCount() public view returns (uint256) {
        return count;
    }
}

部署后替换 CONTRACT_ADDRESS 即可。


🧩 6.4 实现后的完整流程

rust 复制代码
sequenceDiagram
用户 ->> DApp: 打开页面
DApp ->> 钱包: 连接账户
钱包 ->> DApp: 返回地址
用户 ->> DApp: 点击 +1
DApp ->> 钱包: 请求签名交易
钱包 ->> 用户: 弹窗确认
用户 ->> 钱包: 同意
钱包 ->> 区块链: 发送交易
区块链 ->> DApp: 返回确认
DApp ->> 用户: 更新计数

🧾 6.5 已经学会了什么

✅ 与钱包交互

✅ 调用合约的读写方法

✅ 等待交易确认

✅ 实现一个真实的 Web3 前端 DApp


太棒了 🙌

接下来我们进入 Web3 前端全图解实战教程(进阶篇)第 7~10 章

这是从"能跑 DApp"到"能做完整项目"的关键阶段。

你将学到:

  • ERC20 代币读写与转账
  • NFT 铸造(Mint)实战
  • 钱包登录验证(签名身份)
  • 网络与链切换(Mainnet/Testnet/自定义链)

🪙 第 7 章:与 ERC20 代币交互


🎯 7.1 ERC20 是什么?

ERC20 是以太坊上最常见的代币标准。

定义了一组通用接口,让所有钱包、DApp 都能识别并交互。

常见函数:

方法 作用
totalSupply() 代币总量
balanceOf(address) 查询余额
transfer(to, amount) 转账
approve(spender, amount) 授权第三方支出
transferFrom(from, to, amount) 从授权账户转账

📘 ERC20 概念图

css 复制代码
graph LR
A[用户钱包] -->|transfer| B[接收方]
A -->|approve| C[智能合约]
C -->|transferFrom| B

🧱 7.2 ERC20 示例合约(简化版)

ini 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyToken {
    string public name = "DemoToken";
    string public symbol = "DMT";
    uint8 public decimals = 18;
    uint256 public totalSupply = 1000000 * 10 ** uint256(decimals);

    mapping(address => uint256) public balanceOf;

    constructor() {
        balanceOf[msg.sender] = totalSupply;
    }

    function transfer(address to, uint256 amount) public returns (bool) {
        require(balanceOf[msg.sender] >= amount, "余额不足");
        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;
        return true;
    }
}

💻 7.3 前端查询余额与转账

ini 复制代码
import { ethers } from "ethers";

const ERC20_ABI = [
  "function name() view returns (string)",
  "function symbol() view returns (string)",
  "function balanceOf(address) view returns (uint)",
  "function transfer(address to, uint amount)"
];

const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const token = new ethers.Contract("0xTokenAddress", ERC20_ABI, signer);

const name = await token.name();
const balance = await token.balanceOf(await signer.getAddress());
console.log(`${name} 余额:`, ethers.formatUnits(balance, 18));

const tx = await token.transfer("0xReceiver", ethers.parseUnits("1", 18));
await tx.wait();
console.log("✅ 转账完成");

💡 小贴士

  • 所有 ERC20 都有相同接口;
  • 只需换地址,就能与任何代币交互;
  • ethers.formatUnits() 用于格式化代币数(带小数)。

🔄 7.4 前端展示代币信息

javascript 复制代码
function TokenBalance() {
  const [balance, setBalance] = useState("0");

  useEffect(() => {
    async function fetchBalance() {
      const provider = new ethers.BrowserProvider(window.ethereum);
      const signer = await provider.getSigner();
      const token = new ethers.Contract(TOKEN_ADDR, ERC20_ABI, provider);
      const bal = await token.balanceOf(await signer.getAddress());
      setBalance(ethers.formatUnits(bal, 18));
    }
    fetchBalance();
  }, []);

  return <p>你的代币余额:{balance} DMT</p>;
}

🖼️ 第 8 章:NFT(ERC721)铸造实战


🧠 8.1 NFT 是什么?

NFT(Non-Fungible Token)= "独一无二的数字物品"。

📘 类比:

ERC20 是"货币",NFT 是"收藏品"。


🧩 ERC721 交互结构图

css 复制代码
graph TD
A[前端DApp] -->|mint| B[NFT合约]
B -->|生成唯一ID| C[TokenURI JSON]
C -->|包含| D[图片/元数据]

💎 8.2 合约示例(简化 NFT Mint)

typescript 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract MyNFT is ERC721 {
    uint256 public nextId = 1;

    constructor() ERC721("MyNFT", "MNFT") {}

    function mint() public {
        _mint(msg.sender, nextId);
        nextId++;
    }
}

💻 8.3 前端 Mint 逻辑

ini 复制代码
import { ethers } from "ethers";

const NFT_ABI = [
  "function mint()",
  "function nextId() view returns (uint256)"
];

const NFT_ADDRESS = "0xNFTAddress";

async function mintNFT() {
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();
  const nft = new ethers.Contract(NFT_ADDRESS, NFT_ABI, signer);
  const tx = await nft.mint();
  await tx.wait();
  console.log("✅ Mint 成功!");
}

🖼️ 前端页面效果图

css 复制代码
graph LR
A[Mint 按钮] --> B[钱包签名确认]
B --> C[区块链生成 Token ID]
C --> D[用户获得新 NFT]

🧠 8.4 显示用户持有的 NFT

如果 NFT 绑定了 metadata URI:

json 复制代码
{
  "name": "MyNFT #1",
  "image": "https://gateway.pinata.cloud/ipfs/xxx",
  "description": "First NFT"
}

前端就可以读取并展示:

ini 复制代码
const uri = await nft.tokenURI(1);
const metadata = await fetch(uri).then(res => res.json());
console.log(metadata.image);

然后用 <img src={metadata.image} /> 展示。


🔑 第 9 章:钱包登录与签名验证


💬 9.1 为什么 DApp 不需要密码登录?

Web3 登录不靠用户名密码,而靠钱包地址 + 签名。

📘 原理:

前端生成一条随机消息 → 钱包签名 → 后端验证 → 证明你是钱包主人。


⚙️ 登录流程图

rust 复制代码
sequenceDiagram
用户 ->> 前端: 点击"登录"
前端 ->> 钱包: 请求签名消息
钱包 ->> 用户: 弹窗确认
用户 ->> 钱包: 同意签名
钱包 ->> 前端: 返回签名结果
前端 ->> 后端: 发送签名 & 地址
后端 ->> 前端: 验证成功,返回 Token

🧱 9.2 前端签名消息

javascript 复制代码
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const address = await signer.getAddress();

const message = `登录验证,时间戳: ${Date.now()}`;
const signature = await signer.signMessage(message);

// 发送到后端验证
await fetch("/api/verify", {
  method: "POST",
  body: JSON.stringify({ address, message, signature })
});

🔍 9.3 后端验证签名(Node.js)

javascript 复制代码
import { verifyMessage } from "ethers";

app.post("/verify", (req, res) => {
  const { address, message, signature } = req.body;
  const signer = verifyMessage(message, signature);
  if (signer.toLowerCase() === address.toLowerCase()) {
    res.json({ success: true });
  } else {
    res.status(401).json({ success: false });
  }
});

✅ 9.4 登录结果

  • 验证成功 → 颁发 JWT / session
  • 验证失败 → 提示"签名无效"

这就是 Web3 登录的无密码体验!


🌐 第 10 章:网络切换与多链支持


🔄 10.1 为什么要支持多链?

Web3 应用可能运行在:

  • Ethereum 主网(mainnet)
  • 测试网(Sepolia、Holesky)
  • 其他链(Polygon、BSC、Arbitrum)

🌍 多链架构图

css 复制代码
graph TD
A[DApp前端] --> B[MetaMask Provider]
B -->|switchNetwork| C[不同链RPC]
C --> D[目标链智能合约]

🧱 10.2 切换网络示例

csharp 复制代码
async function switchToSepolia() {
  await window.ethereum.request({
    method: "wallet_switchEthereumChain",
    params: [{ chainId: "0xaa36a7" }] // Sepolia
  });
}

🧩 10.3 添加自定义网络

php 复制代码
await window.ethereum.request({
  method: "wallet_addEthereumChain",
  params: [{
    chainId: "0x89", // Polygon
    chainName: "Polygon Mainnet",
    rpcUrls: ["https://polygon-rpc.com/"],
    nativeCurrency: { name: "MATIC", symbol: "MATIC", decimals: 18 },
    blockExplorerUrls: ["https://polygonscan.com/"]
  }]
});

✅ 10.4 网络检测与监听

javascript 复制代码
window.ethereum.on("chainChanged", (chainId) => {
  console.log("网络切换为:", chainId);
  window.location.reload();
});

🎓 结语:从前端到 Web3 工程师的跃迁

你现在已经掌握:

能力 说明
⚙️ ethers.js 基础 与合约交互(读/写)
🔐 钱包签名 交易签名、登录验证
💰 Token ERC20 读写、转账
🖼️ NFT ERC721 Mint、展示
🌐 多链支持 钱包网络切换

接下来建议的 进阶路线

  1. 深入合约开发

    学习 Solidity + Hardhat 测试与部署

    官方教程:docs.ethers.org

    Solidity 文档:docs.soliditylang.org

  2. 使用前端框架集成

    • Next.js + Wagmi + RainbowKit
    • 支持多钱包连接与自动链检测
  3. 真实项目实战

    • Token Dashboard
    • NFT Mint 平台
    • Web3 登录门户

太好了 💪

我们现在进入整套《前端开发者的 Web3 全图解实战教程》第三部分:综合项目篇(第 11~15 章)

这是让你从"懂概念" → "能做项目" → "能上线"的阶段。

📘 目标:带你手把手完成三个可上线的 Web3 应用。

每章都有清晰架构图 + 合约 + 前端代码 + 讲解。


🚀 第 11 章:Token Dashboard(代币看板)


🎯 功能目标

构建一个代币仪表盘,支持:

  • 连接钱包
  • 显示代币余额(多个 ERC20)
  • 一键转账
  • 实时监听余额变化

🧩 系统架构图

css 复制代码
graph TD
A[React DApp 前端] --> B[ethers.js]
B --> C[智能合约(ERC20)]
B --> D[钱包 Provider]
D --> E[区块链节点]

🧱 11.1 前端布局设计

xml 复制代码
<div className="p-8 max-w-md mx-auto space-y-6">
  <h2 className="text-2xl font-bold">💰 Token Dashboard</h2>
  <button onClick={connectWallet}>连接钱包</button>

  <div>
    <h3>账户:{account}</h3>
    <ul>
      {tokens.map(t => (
        <li key={t.symbol}>
          {t.symbol}: {t.balance}
        </li>
      ))}
    </ul>
  </div>

  <input placeholder="目标地址" value={to} onChange={e => setTo(e.target.value)} />
  <input placeholder="数量" value={amount} onChange={e => setAmount(e.target.value)} />
  <button onClick={transfer}>转账</button>
</div>

🧩 11.2 查询多个代币余额

ini 复制代码
const TOKENS = [
  { symbol: "USDT", address: "0xYourToken1" },
  { symbol: "DMT", address: "0xYourToken2" }
];

const ABI = ["function balanceOf(address) view returns(uint)"];

async function loadBalances() {
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();
  const addr = await signer.getAddress();

  const results = await Promise.all(
    TOKENS.map(async (t) => {
      const token = new ethers.Contract(t.address, ABI, provider);
      const balance = await token.balanceOf(addr);
      return { ...t, balance: ethers.formatUnits(balance, 18) };
    })
  );

  setTokens(results);
}

🧩 11.3 实现代币转账

csharp 复制代码
async function transfer() {
  const token = new ethers.Contract(selectedToken.address, ABI_TRANSFER, signer);
  const tx = await token.transfer(to, ethers.parseUnits(amount, 18));
  await tx.wait();
  alert("✅ 转账成功");
  loadBalances();
}

🔄 实时监听余额变化

dart 复制代码
window.ethereum.on("accountsChanged", loadBalances);
window.ethereum.on("chainChanged", () => window.location.reload());

🎨 效果预览图

css 复制代码
graph TD
A[Token Dashboard]
A --> B[钱包地址]
A --> C[Token 列表]
A --> D[输入转账信息]
A --> E[MetaMask 确认]

🖼️ 第 12 章:NFT Mint 平台(图像上传 + 元数据存储)


🎯 功能目标

用户可以:

  • 上传图片
  • 填写 NFT 名称与描述
  • 上传元数据到 IPFS(Pinata)
  • 调用合约 mint()

🧩 系统架构图

css 复制代码
graph TD
A[React前端] -->|upload| B[Pinata IPFS]
A -->|mint| C[NFT合约]
C --> D[区块链]
D --> E[钱包]

⚙️ 12.1 准备 Pinata API

  1. 注册 www.pinata.cloud/
  2. 获取 JWT Token
  3. 前端使用 axios 上传文件

💻 12.2 上传文件到 IPFS

javascript 复制代码
import axios from "axios";

const PINATA_JWT = "Bearer <Your Pinata JWT>";

async function uploadToIPFS(file) {
  const formData = new FormData();
  formData.append("file", file);

  const res = await axios.post("https://api.pinata.cloud/pinning/pinFileToIPFS", formData, {
    headers: {
      Authorization: PINATA_JWT,
      "Content-Type": "multipart/form-data",
    },
  });

  return `https://gateway.pinata.cloud/ipfs/${res.data.IpfsHash}`;
}

💾 12.3 上传 Metadata 并 Mint

ini 复制代码
async function mintNFT(file, name, desc) {
  const imageUrl = await uploadToIPFS(file);
  const metadata = { name, description: desc, image: imageUrl };

  const res = await axios.post("https://api.pinata.cloud/pinning/pinJSONToIPFS", metadata, {
    headers: { Authorization: PINATA_JWT },
  });

  const uri = `https://gateway.pinata.cloud/ipfs/${res.data.IpfsHash}`;
  const contract = new ethers.Contract(NFT_ADDRESS, ABI, signer);
  const tx = await contract.mint(uri);
  await tx.wait();
  console.log("✅ NFT Mint 成功");
}

🖼️ 用户交互图

rust 复制代码
sequenceDiagram
用户 ->> DApp: 上传图片
DApp ->> Pinata: 上传到 IPFS
Pinata ->> DApp: 返回 CID
DApp ->> 合约: 调用 mint(uri)
合约 ->> 区块链: 生成新 NFT
区块链 ->> 用户: NFT 铸造完成

🔐 第 13 章:Web3 登录门户(签名验证 + JWT)


🎯 功能目标

  • 用户点击"登录钱包"
  • 前端生成签名消息
  • 后端验证签名
  • 返回 JWT Token

🧩 13.1 前端签名登录

ini 复制代码
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const address = await signer.getAddress();
const nonce = Date.now();

const message = `Login verification for ${address}, nonce: ${nonce}`;
const signature = await signer.signMessage(message);

const res = await fetch("/api/login", {
  method: "POST",
  body: JSON.stringify({ address, message, signature }),
});
const { token } = await res.json();
localStorage.setItem("jwt", token);

🧱 13.2 后端验证并生成 JWT

javascript 复制代码
import jwt from "jsonwebtoken";
import { verifyMessage } from "ethers";

app.post("/api/login", (req, res) => {
  const { address, message, signature } = req.body;
  const signer = verifyMessage(message, signature);

  if (signer.toLowerCase() === address.toLowerCase()) {
    const token = jwt.sign({ address }, process.env.JWT_SECRET, { expiresIn: "1h" });
    res.json({ token });
  } else {
    res.status(401).json({ error: "Invalid signature" });
  }
});

🔒 登录流程图

css 复制代码
graph TD
A[前端] --> B[钱包签名]
B --> C[后端验证]
C --> D[JWT 生成]
D --> E[返回 Token]
E --> A[用户已登录]

🌐 第 14 章:多链钱包支持与持久化


🎯 目标

  • 自动检测网络
  • 一键切换链
  • 记住用户登录状态

⚙️ 14.1 使用 wagmi + RainbowKit

bash 复制代码
npm install wagmi viem @rainbow-me/rainbowkit
javascript 复制代码
import { WagmiConfig, createConfig, configureChains } from "wagmi";
import { sepolia, polygon } from "wagmi/chains";
import { RainbowKitProvider, getDefaultWallets } from "@rainbow-me/rainbowkit";

const { chains, publicClient } = configureChains([sepolia, polygon], []);
const { connectors } = getDefaultWallets({ appName: "Web3App", chains });
const config = createConfig({ connectors, publicClient });

export default function App() {
  return (
    <WagmiConfig config={config}>
      <RainbowKitProvider chains={chains}>
        <YourDapp />
      </RainbowKitProvider>
    </WagmiConfig>
  );
}

🌍 14.2 自动记忆钱包连接状态

RainbowKit 默认会持久化用户钱包登录状态到 localStorage。

刷新后仍能保持连接。


☁️ 第 15 章:部署上线(Vercel + 公网链)


🎯 步骤总览

  1. 部署合约到 Sepolia 测试网
  2. 配置前端 .env(合约地址、API)
  3. 将 React/Next.js 应用上传至 Vercel
  4. 绑定域名,公开访问

⚙️ 15.1 部署合约

使用 Hardhat:

arduino 复制代码
npx hardhat run scripts/deploy.js --network sepolia

返回地址:

yaml 复制代码
Contract deployed at: 0x1234abcd...

🧩 15.2 配置前端环境变量

ini 复制代码
VITE_CONTRACT_ADDRESS=0x1234abcd...
VITE_PINATA_JWT=Bearer xxxxxx

☁️ 15.3 部署到 Vercel

arduino 复制代码
npm run build
vercel --prod

Vercel 会自动打包 React 项目生成一个在线访问链接。


📊 系统架构图(全流程)

css 复制代码
graph TD
A[用户浏览器] --> B[前端 React DApp (Vercel)]
B --> C[钱包 MetaMask]
B --> D[区块链网络 (Sepolia / Polygon)]
B --> E[后端验证服务 (JWT 登录)]
E --> F[IPFS Pinata 存储]

🎓 完成总结

你现在已经能独立构建并上线一个完整的 Web3 前端项目:

✅ Token 仪表盘

✅ NFT Mint 平台

✅ 钱包登录验证

✅ 多链切换

✅ Vercel 部署


接下来推荐:

相关推荐
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte5 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc