前端开发者的 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 部署


接下来推荐:

相关推荐
写代码的皮筏艇5 小时前
CSS属性继承与特殊值
前端·css
kevlin_coder5 小时前
🚀 实现同一个滚动区域包含多个虚拟滚动列表
前端·javascript
金梦人生5 小时前
JS 性能优化
前端·javascript
我有一棵树5 小时前
使用Flex布局实现多行多列,每个列宽度相同
前端·css·html·scss·flex
浪裡遊5 小时前
React开发模式解析:JSX语法与生命周期管理
前端·javascript·react.js·前端框架·ecmascript
用户877244753965 小时前
Lubanno7UniverSheet:开放底层能力,让你的表格需求 “不设限”
前端
张可爱5 小时前
ES6奶茶铺版通俗笔记 🍵✨
前端
用户877244753965 小时前
Lubanno7UniverSheet:选择命令式,为了真正的跨框架通用
前端
Aoda5 小时前
从痛点到落地:PawHaven 的 Monorepo 架构设计
前端·javascript