Web3/DApp / 联盟链 / 应用场景
Web3 概念与演进
Web1.0 到 Web3.0 演进对比
互联网经历了三个主要阶段的演进,每一阶段在技术架构、所有权模型和用户体验上都有根本性差异。
| 维度 | Web1.0(只读) | Web2.0(读写) | Web3.0(读写+拥有) |
|---|---|---|---|
| 时间 | 1990-2004 | 2004-2020 | 2020 至今 |
| 内容生产 | 只读,仅企业发布内容 | 用户生成内容(UGC) | 用户拥有内容与数据 |
| 网络模型 | 客户端-服务器 | 平台-用户(中心化) | 点对点(去中心化) |
| 数据所有权 | 归平台所有 | 归平台所有 | 归用户所有 |
| 身份系统 | 无统一身份 | 平台账号(OAuth) | 去中心化身份(DID) |
| 支付方式 | 无原生支付 | 第三方支付(支付宝/PayPal) | 原生加密支付 |
| 代表性技术 | HTTP、HTML、门户网站 | AJAX、社交媒体、移动App | 区块链、智能合约、IPFS |
| 代表性产品 | Yahoo、Netscape、新浪 | Facebook、微信、抖音 | Uniswap、OpenSea、ENS |
| 安全性 | 依赖服务器安全 | 平台安全+隐私泄露风险 | 密码学保证+抗审查 |
| 可组合性 | 低 | 中(API 调用) | 高(合约乐高 Lego) |
Web3 核心特征
去中心化(Decentralization)
区块链网络由全球分布的节点共同维护,没有单点故障和控制中心。数据存储在分布式账本上,所有节点持有完整副本,单个节点的退出不影响网络运行。
Web2 架构(中心化): Web3 架构(去中心化):
┌───服务器───┐ ┌───[节点]───┐
│ │ / │ \
┌───┴───┐ ┌───┴───┐ ┌───[节点]───[节点]───[节点]
│ 客户端 │ │ 客户端 │ │ │ │ │ │
└───────┘ └───────┘ [节点]─[节点]─[节点]─[节点]用户主权(User Sovereignty)
用户通过私钥完全控制自己的资产和数据,无需信任第三方。私钥即身份——"Not your keys, not your coins"。用户数据可携带,在不同应用之间无缝迁移。
原生支付(Native Payments)
区块链原生支持价值转移,无需中间机构。智能合约可以自动执行支付逻辑,实现可编程货币。Gas 机制保障网络资源公平使用。
抗审查(Censorship Resistance)
任何实体无法阻止特定地址的交易上链。去中心化应用的前端可通过 IPFS 托管,域名通过 ENS 解析,应用逻辑通过智能合约执行,实现全栈抗审查。
DApp 架构
去中心化应用(DApp)由三层架构组成:
┌─────────────────────────────────────────┐
│ 前端层(Frontend) │
│ React / Vue + ethers.js / wagmi │
│ IPFS 托管 / ENS 域名 │
├─────────────────────────────────────────┤
│ 合约层(Smart Contract) │
│ Solidity / Vyper │
│ EVM 兼容链(Ethereum / Polygon / BSC) │
├─────────────────────────────────────────┤
│ 链下层(Off-chain) │
│ IPFS / Filecoin(存储) │
│ The Graph(索引) │
│ Chainlink(预言机) │
└─────────────────────────────────────────┘前端层:用户交互界面,通过 RPC 节点与区块链通信,使用钱包(如 MetaMask)签署交易。
合约层:智能合约是部署在区块链上的程序,定义了 DApp 的业务逻辑。合约一旦部署不可篡改(或可通过代理合约升级)。
链下层:由于区块链存储成本高昂,大文件(图片、文档)存储在去中心化存储网络(IPFS/Filecoin)中,链上仅存内容哈希(CID)。链上数据通过索引协议(The Graph)高效查询。外部数据通过预言机(Oracle)喂入合约。
DApp 开发框架
Hardhat 项目搭建
Hardhat 是以太坊生态最流行的开发框架,支持编译、测试、部署和调试。
# 初始化项目
mkdir my-dapp && cd my-dapp
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
# 初始化 Hardhat 项目
npx hardhat init
# 选择 "Create a JavaScript project" 或 "Create a TypeScript project"项目结构:
my-dapp/
├── contracts/ # Solidity 合约文件
├── test/ # 测试文件
├── scripts/ # 部署脚本
├── hardhat.config.js # 配置文件
├── ignition/ # Hardhat Ignition 部署模块
└── artifacts/ # 编译产物(ABI + Bytecode)Hardhat 配置文件:
// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: {
version: "0.8.24",
settings: {
optimizer: { enabled: true, runs: 200 },
},
},
networks: {
hardhat: {
chainId: 31337,
},
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY || "",
},
};合约编写与测试
示例:ERC20 代币合约
// contracts/MyToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
uint256 public constant MAX_SUPPLY = 1_000_000 * 10 ** 18;
constructor() ERC20("MyToken", "MTK") Ownable(msg.sender) {
_mint(msg.sender, 100_000 * 10 ** 18);
}
function mint(address to, uint256 amount) external onlyOwner {
require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
_mint(to, amount);
}
function burn(uint256 amount) external {
_burn(msg.sender, amount);
}
}测试用例
// test/MyToken.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyToken", function () {
let MyToken, myToken, owner, addr1;
beforeEach(async function () {
[owner, addr1] = await ethers.getSigners();
MyToken = await ethers.getContractFactory("MyToken");
myToken = await MyToken.deploy();
await myToken.waitForDeployment();
});
it("should deploy with correct name and symbol", async function () {
expect(await myToken.name()).to.equal("MyToken");
expect(await myToken.symbol()).to.equal("MTK");
});
it("should mint initial supply to owner", async function () {
const ownerBalance = await myToken.balanceOf(owner.address);
expect(ownerBalance).to.equal(ethers.parseEther("100000"));
});
it("should allow owner to mint tokens", async function () {
await myToken.mint(addr1.address, ethers.parseEther("1000"));
expect(await myToken.balanceOf(addr1.address)).to.equal(
ethers.parseEther("1000")
);
});
it("should not allow non-owner to mint", async function () {
await expect(
myToken.connect(addr1).mint(addr1.address, ethers.parseEther("1000"))
).to.be.revertedWithCustomError(myToken, "OwnableUnauthorizedAccount");
});
});运行测试:
npx hardhat test
npx hardhat coverage # 测试覆盖率报告部署脚本
// scripts/deploy.js
const { ethers } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying with account:", deployer.address);
const MyToken = await ethers.getContractFactory("MyToken");
const myToken = await MyToken.deploy();
await myToken.waitForDeployment();
const contractAddress = await myToken.getAddress();
console.log("MyToken deployed to:", contractAddress);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});npx hardhat run scripts/deploy.js --network sepolia前端集成
前端通过 JavaScript 库与智能合约交互,主流方案包括 ethers.js、wagmi 和 viem。
ethers.js
// 使用 ethers.js v6
import { ethers } from "ethers";
// 连接 MetaMask 钱包
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
// 合约 ABI 和地址
const contractAddress = "0x...";
const abi = [
"function balanceOf(address owner) view returns (uint256)",
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 value)",
];
const contract = new ethers.Contract(contractAddress, abi, signer);
// 读取数据(只读调用,无需 Gas)
const balance = await contract.balanceOf(userAddress);
// 写入数据(需要 Gas,通过钱包签名)
const tx = await contract.transfer(recipientAddress, amount);
await tx.wait(); // 等待交易确认
console.log("Transaction hash:", tx.hash);wagmi + viem (React)
wagmi 是针对 React 的现代 Web3 交互框架,底层使用 viem 代替 ethers.js。
npm install wagmi viem @tanstack/react-query// WagmiProvider.tsx
import { WagmiProvider, createConfig, http } from "wagmi";
import { mainnet, sepolia } from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { injected } from "wagmi/connectors";
const config = createConfig({
chains: [mainnet, sepolia],
connectors: [injected()],
transports: {
[mainnet.id]: http(),
[sepolia.id]: http(),
},
});
const queryClient = new QueryClient();
export function Providers({ children }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
);
}// TokenBalance.tsx
import { useAccount, useReadContract, useWriteContract } from "wagmi";
import { formatEther, parseEther } from "viem";
const TOKEN_ABI = [
{
inputs: [{ name: "account", type: "address" }],
name: "balanceOf",
outputs: [{ name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
{
inputs: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
],
name: "transfer",
outputs: [{ name: "", type: "bool" }],
stateMutability: "nonpayable",
type: "function",
},
] as const;
function TokenBalance({ contractAddress }: { contractAddress: `0x${string}` }) {
const { address, isConnected } = useAccount();
const { data: balance, refetch } = useReadContract({
address: contractAddress,
abi: TOKEN_ABI,
functionName: "balanceOf",
args: address ? [address] : undefined,
});
const { writeContract } = useWriteContract();
const handleTransfer = async (to: `0x${string}`, amount: string) => {
await writeContract({
address: contractAddress,
abi: TOKEN_ABI,
functionName: "transfer",
args: [to, parseEther(amount)],
});
};
if (!isConnected) return <div>请连接钱包</div>;
return (
<div>
<p>余额: {balance ? formatEther(balance) : "0"} MTK</p>
</div>
);
}Vue + ethers.js
<!-- TokenCard.vue -->
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ethers } from "ethers";
const balance = ref<string>("0");
const account = ref<string>("");
const provider = ref<ethers.BrowserProvider | null>(null);
const contractAddress = "0x...";
const abi = [
"function balanceOf(address owner) view returns (uint256)",
"function transfer(address to, uint256 amount) returns (bool)",
];
onMounted(async () => {
if (window.ethereum) {
provider.value = new ethers.BrowserProvider(window.ethereum);
const accounts = await provider.value.send("eth_requestAccounts", []);
account.value = accounts[0];
await fetchBalance();
}
});
async function fetchBalance() {
if (!provider.value || !account.value) return;
const contract = new ethers.Contract(
contractAddress,
abi,
provider.value
);
const bal = await contract.balanceOf(account.value);
balance.value = ethers.formatEther(bal);
}
</script>
<template>
<div class="token-card">
<p>地址: {{ account }}</p>
<p>余额: {{ balance }} MTK</p>
</div>
</template>IPFS 去中心化存储
IPFS(InterPlanetary File System)是一种点对点分布式文件系统,通过内容寻址(CID)替代传统的位置寻址(URL)。
IPFS 核心概念
- CID(Content Identifier):文件内容的哈希值,内容不变则 CID 不变
- MFS(Mutable File System):可变文件系统,类似 Unix 文件目录
- Pinning:固定文件,防止被 GC 回收
- Gateway:通过 HTTP 访问 IPFS 内容(如
https://ipfs.io/ipfs/<CID>)
文件上传示例
// 使用 kubo RPC API 上传文件到 IPFS
import { create } from "kubo-rpc-client";
const ipfs = create({ url: "http://localhost:5001/api/v0" });
async function uploadToIPFS(file) {
const { cid } = await ipfs.add(file, {
pin: true,
});
console.log("File CID:", cid.toString());
return cid.toString();
}
// 上传 JSON 元数据
async function uploadMetadata(metadata) {
const { cid } = await ipfs.add(JSON.stringify(metadata));
return cid.toString();
}Pinata 托管服务
// 使用 Pinata 托管 IPFS 文件
const pinataSDK = require("@pinata/sdk");
const pinata = new pinataSDK(process.env.PINATA_API_KEY, process.env.PINATA_SECRET_KEY);
async function pinFileToIPFS(filePath) {
const fs = require("fs");
const readableStream = fs.createReadStream(filePath);
const result = await pinata.pinFileToIPFS(readableStream, {
pinataMetadata: { name: "my-file" },
});
return `https://gateway.pinata.cloud/ipfs/${result.IpfsHash}`;
}
async function pinJSONToIPFS(json) {
const result = await pinata.pinJSONToIPFS(json);
return `ipfs://${result.IpfsHash}`;
}Filecoin 持久化存储
Filecoin 是 IPFS 的激励层,通过存储证明机制确保数据长期保存。
// 使用 Filecoin 客户端 API
// 安装: npm install @filecoin-shipyard/lotus-client-rpc
const { LotusRPC } = require("@filecoin-shipyard/lotus-client-rpc");
const { NodejsProvider } = require("@filecoin-shipyard/lotus-client-provider-nodejs");
const provider = new NodejsProvider("http://127.0.0.1:1234/rpc/v0");
const client = new LotusRPC(provider);
async function makeDeal(cid, duration) {
// 创建存储交易
const deal = await client.clientStartDeal({
Data: { TransferType: "manual", Root: { "/": cid } },
Wallet: await client.walletDefaultAddress(),
Miner: "f01234", // 矿工 ID
EpochPrice: "250000000", // 每 epoch 价格(attoFIL)
MinBlocksDuration: duration, // 存储时长(epochs)
});
return deal;
}在智能合约中引用 IPFS
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract IPFSNFT {
struct Metadata {
string name;
string description;
string imageCID; // IPFS CID
string externalCID;
}
mapping(uint256 => Metadata) public tokenMetadata;
event MetadataUpdated(uint256 indexed tokenId, string imageCID);
function mintWithMetadata(
uint256 tokenId,
string calldata name,
string calldata description,
string calldata imageCID
) external {
tokenMetadata[tokenId] = Metadata(name, description, imageCID, "");
emit MetadataUpdated(tokenId, imageCID);
}
function tokenURI(uint256 tokenId) external view returns (string memory) {
Metadata storage meta = tokenMetadata[tokenId];
return string(abi.encodePacked(
"ipfs://", meta.imageCID
));
}
}The Graph 链上数据索引
The Graph 是一个去中心化索引协议,通过 Subgraph 将链上事件转换为可高效查询的数据。
Subgraph 结构
subgraph/
├── schema.graphql # 数据模型定义
├── src/
│ └── mapping.ts # 事件处理映射
├── subgraph.yaml # 配置文件
├── package.json
└── tsconfig.jsonschema.graphql
type Token @entity {
id: ID!
owner: Bytes!
name: String!
symbol: String!
totalSupply: BigInt!
transfers: [Transfer!]! @derivedFrom(field: "token")
}
type Transfer @entity(immutable: true) {
id: ID!
token: Token!
from: Bytes!
to: Bytes!
amount: BigInt!
timestamp: BigInt!
blockNumber: BigInt!
}subgraph.yaml
specVersion: 1.0.0
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: MyToken
network: sepolia
source:
address: "0x..."
abi: MyToken
startBlock: 1234567
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Token
- Transfer
abis:
- name: MyToken
file: ./abis/MyToken.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
file: ./src/mapping.tsmapping.ts
import { BigInt } from "@graphprotocol/graph-ts";
import {
Transfer as TransferEvent,
MyToken,
} from "../generated/MyToken/MyToken";
import { Token, Transfer } from "../generated/schema";
export function handleTransfer(event: TransferEvent): void {
let token = Token.load(event.address.toHexString());
if (!token) {
token = new Token(event.address.toHexString());
let contract = MyToken.bind(event.address);
token.name = contract.name();
token.symbol = contract.symbol();
token.totalSupply = contract.totalSupply();
token.owner = event.params.from;
}
token.save();
let transfer = new Transfer(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
transfer.token = token.id;
transfer.from = event.params.from;
transfer.to = event.params.to;
transfer.amount = event.params.value;
transfer.timestamp = event.block.timestamp;
transfer.blockNumber = event.block.number;
transfer.save();
}前端查询
import { request, gql } from "graphql-request";
const SUBGRAPH_URL = "https://api.studio.thegraph.com/query/<ID>/<SUBGRAPH>/<VERSION>";
const TRANSFERS_QUERY = gql`
query GetTransfers($first: Int!) {
transfers(first: $first, orderBy: timestamp, orderDirection: desc) {
id
from
to
amount
timestamp
}
}
`;
async function getTransfers() {
const data = await request(SUBGRAPH_URL, TRANSFERS_QUERY, { first: 50 });
return data.transfers;
}Chainlink 预言机
预言机(Oracle)将链下数据安全地引入链上。Chainlink 是最大的去中心化预言机网络。
获取价格数据
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract PriceConsumer {
AggregatorV3Interface internal priceFeed;
// Sepolia ETH/USD 喂价合约地址
constructor() {
priceFeed = AggregatorV3Interface(
0x694AA1769357215DE4FAC081bf1f309aDC325306
);
}
function getLatestPrice() public view returns (int) {
(
/* uint80 roundID */,
int price,
/* uint startedAt */,
/* uint timeStamp */,
/* uint80 answeredInRound */
) = priceFeed.latestRoundData();
return price / 1e8; // 转换为美元价格
}
}Chainlink VRF(可验证随机函数)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
contract RandomWinner is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface COORDINATOR;
uint64 private subscriptionId;
bytes32 private keyHash = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c;
uint32 private callbackGasLimit = 100000;
mapping(uint256 => address) public requestToSender;
mapping(address => uint256) public randomResults;
event RequestRandom(address indexed sender, uint256 requestId);
event RandomResult(address indexed sender, uint256 randomNumber);
constructor(uint64 _subscriptionId) VRFConsumerBaseV2(0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625) {
COORDINATOR = VRFCoordinatorV2Interface(0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625);
subscriptionId = _subscriptionId;
}
function requestRandomNumber() external returns (uint256 requestId) {
requestId = COORDINATOR.requestRandomWords(
keyHash,
subscriptionId,
3, // minimumRequestConfirmations
callbackGasLimit,
1 // numWords
);
requestToSender[requestId] = msg.sender;
emit RequestRandom(msg.sender, requestId);
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
address sender = requestToSender[requestId];
randomResults[sender] = randomWords[0];
emit RandomResult(sender, randomWords[0]);
}
}钱包集成
MetaMask 连接
window.ethereum 基础连接
// 检查 MetaMask 是否安装
if (typeof window.ethereum !== "undefined") {
console.log("MetaMask is installed!");
} else {
console.log("请安装 MetaMask: https://metamask.io");
}
// 请求连接钱包
async function connectWallet() {
try {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
const account = accounts[0];
console.log("Connected account:", account);
return account;
} catch (error) {
if (error.code === 4001) {
// EIP-1193 User Rejected Request
console.log("用户拒绝了连接请求");
}
throw error;
}
}
// 监听账户变化
window.ethereum.on("accountsChanged", (accounts) => {
if (accounts.length === 0) {
console.log("钱包已断开连接");
} else {
console.log("账户切换为:", accounts[0]);
}
});
// 监听链切换
window.ethereum.on("chainChanged", (chainId) => {
console.log("链切换为:", parseInt(chainId, 16));
// 建议刷新页面
window.location.reload();
});
// 监听断开连接
window.ethereum.on("disconnect", (error) => {
console.log("钱包断开:", error);
});获取账户与余额
async function getAccountInfo() {
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const address = await signer.getAddress();
// 获取 ETH 余额
const balanceWei = await provider.getBalance(address);
const balanceEth = ethers.formatEther(balanceWei);
// 获取链信息
const network = await provider.getNetwork();
const chainId = Number(network.chainId);
return { address, balanceEth, chainId };
}切换链
async function switchChain(chainIdHex) {
try {
await window.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: chainIdHex }],
});
} catch (switchError) {
// 如果链不存在,添加链
if (switchError.code === 4902) {
await window.ethereum.request({
method: "wallet_addEthereumChain",
params: [
{
chainId: chainIdHex,
chainName: "Polygon Mainnet",
nativeCurrency: {
name: "MATIC",
symbol: "MATIC",
decimals: 18,
},
rpcUrls: ["https://polygon-rpc.com"],
blockExplorerUrls: ["https://polygonscan.com"],
},
],
});
} else {
throw switchError;
}
}
}
// 切换到 Sepolia 测试网
await switchChain("0xaa36a7"); // chainId 11155111EIP-1193 规范
EIP-1193 定义了以太坊钱包的 JavaScript API 标准,window.ethereum 遵循此规范。
| 方法 | 描述 |
|---|---|
eth_requestAccounts | 请求用户授权访问账户 |
eth_accounts | 返回当前授权的账户列表 |
eth_chainId | 返回当前链 ID |
eth_getBalance | 查询地址余额 |
eth_sendTransaction | 发送交易 |
eth_signTypedData | 结构化数据签名 |
personal_sign | 消息签名 |
wallet_switchEthereumChain | 切换链 |
wallet_addEthereumChain | 添加自定义链 |
// 遵循 EIP-1193 的通用请求封装
class EthereumProvider {
constructor(provider) {
this.provider = provider;
}
async request({ method, params }) {
if (!this.provider) {
throw new Error("Provider not available");
}
try {
const result = await this.provider.request({ method, params });
return result;
} catch (error) {
// EIP-1193 错误格式
const eip1193Error = {
code: error.code,
message: error.message,
data: error.data,
};
throw eip1193Error;
}
}
// 便捷方法
async getAccounts() {
return this.request({ method: "eth_accounts" });
}
async getChainId() {
const chainId = await this.request({ method: "eth_chainId" });
return parseInt(chainId, 16);
}
async getBalance(address) {
return this.request({
method: "eth_getBalance",
params: [address, "latest"],
});
}
}交易签名
eth_sendTransaction
async function sendETH(to, amountInEth) {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
const from = accounts[0];
const amountWei = ethers.parseEther(amountInEth);
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [
{
from,
to,
value: amountWei.toString(16), // 十六进制
gas: "0x5208", // 21000
},
],
});
console.log("Transaction sent:", txHash);
// 等待交易确认
const provider = new ethers.BrowserProvider(window.ethereum);
const receipt = await provider.waitForTransaction(txHash);
console.log("Transaction confirmed:", receipt);
return receipt;
}eth_signTypedData(EIP-712)结构化数据签名
EIP-712 提供结构化、可读的签名数据格式,避免用户误签不可读的哈希。
// 定义 EIP-712 域和类型
const domain = {
name: "MyDApp",
version: "1",
chainId: 1,
verifyingContract: "0x...",
};
const types = {
Order: [
{ name: "seller", type: "address" },
{ name: "buyer", type: "address" },
{ name: "tokenId", type: "uint256" },
{ name: "price", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
};
const message = {
seller: "0x...",
buyer: "0x...",
tokenId: 42,
price: ethers.parseEther("1.5").toString(),
deadline: Math.floor(Date.now() / 1000) + 3600,
};
async function signOrder() {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
// eth_signTypedData_v4 是最新版
const signature = await window.ethereum.request({
method: "eth_signTypedData_v4",
params: [accounts[0], JSON.stringify({ domain, types, message })],
});
console.log("Signature:", signature);
return signature;
}
// 在合约中验证签名// Solidity 端验证 EIP-712 签名
function verifyOrder(
address seller,
address buyer,
uint256 tokenId,
uint256 price,
uint256 deadline,
bytes calldata signature
) external view returns (bool) {
bytes32 digest = _hashTypedDataV4(
keccak256(
abi.encode(
keccak256("Order(address seller,address buyer,uint256 tokenId,uint256 price,uint256 deadline)"),
seller,
buyer,
tokenId,
price,
deadline
)
)
);
address signer = ECDSA.recover(digest, signature);
return signer == seller;
}personal_sign(消息签名)
async function signMessage(message) {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
const signature = await window.ethereum.request({
method: "personal_sign",
params: [message, accounts[0]],
});
// 验证签名
const recovered = ethers.verifyMessage(message, signature);
console.log("Expected:", accounts[0]);
console.log("Recovered:", recovered);
return signature;
}Web3Modal 多钱包连接
Web3Modal 是一个统一的钱包连接组件,支持 MetaMask、WalletConnect、Coinbase Wallet 等多种钱包。
npm install @web3modal/wagmi @web3modal/react wagmi viem @tanstack/react-query// 配置 Web3Modal
import { createWeb3Modal } from "@web3modal/wagmi/react";
import { defaultWagmiConfig } from "@web3modal/wagmi/react/config";
import { WagmiProvider } from "wagmi";
import { mainnet, sepolia } from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
// 1. 项目配置
const projectId = "YOUR_WALLETCONNECT_PROJECT_ID";
const metadata = {
name: "My DApp",
description: "My Web3 Application",
url: "https://mydapp.com",
icons: ["https://mydapp.com/icon.png"],
};
const chains = [mainnet, sepolia] as const;
const config = defaultWagmiConfig({
chains,
projectId,
metadata,
enableInjected: true,
enableEIP6963: true,
enableCoinbase: true,
});
// 2. 创建 Web3Modal
createWeb3Modal({
wagmiConfig: config,
projectId,
enableAnalytics: true,
});
// 3. 在应用根组件中使用
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
</WagmiProvider>
);
}// Connect Button 组件
import { useWeb3Modal } from "@web3modal/wagmi/react";
import { useAccount, useDisconnect } from "wagmi";
function ConnectButton() {
const { open } = useWeb3Modal();
const { address, isConnected } = useAccount();
const { disconnect } = useDisconnect();
if (isConnected) {
return (
<div>
<span>
{address?.slice(0, 6)}...{address?.slice(-4)}
</span>
<button onClick={() => disconnect()}>断开</button>
<button onClick={() => open({ view: "Account" })}>账户详情</button>
</div>
);
}
return <button onClick={() => open()}>连接钱包</button>;
}联盟链:Hyperledger Fabric
Fabric 架构总览
Hyperledger Fabric 是一种许可型(Permissioned)区块链框架,面向企业级应用。与公链不同,Fabric 的节点需要经过身份认证才能加入网络。
┌───────────────────────────────────────────────────────┐
│ Fabric 网络 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Org1 │ │ Org2 │ │ Org3 │ │
│ │ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │ │
│ │ │Peer A│ │ │ │Peer B│ │ │ │Peer C│ │ │
│ │ └──────┘ │ │ └──────┘ │ │ └──────┘ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ┌────┴──────────────┴──────────────┴────┐ │
│ │ Orderer 节点 │ │
│ │ (Raft 共识 / Kafka / PBFT) │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Fabric CA │ │
│ │ (证书颁发 / 身份管理 / MSP) │ │
│ └───────────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘核心组件与角色
Peer 节点(记账节点)
Peer 节点维护账本副本,执行链码(Chaincode),并验证交易。每个 Peer 属于一个组织(Org),一个组织可以有多个 Peer。
- 背书节点(Endorsing Peer):接收客户端交易提案,模拟执行链码,返回背书签名
- 记账节点(Committing Peer):验证 Orderer 发来的区块,将有效区块追加到账本
Orderer 节点(排序节点)
Orderer 节点对交易进行排序并打包成区块,负责共识机制。Orderer 不维护世界状态,不执行链码。常用的共识方式:
- Raft:CFT(Crash Fault Tolerance),生产环境推荐
- Kafka:已弃用,由 Raft 替代
- PBFT:BFT(Byzantine Fault Tolerance),需要额外配置
CA(Certificate Authority,证书颁发机构)
Fabric CA 负责颁发和管理 TLS 证书与 MSP 证书,是权限控制的基础。每个组织有独立的 CA,颁发成员证书和 TLS 证书。
通道(Channel)
通道是 Fabric 的私密通信机制,通道内的 Peer 共享一个独立的账本。通道实现了数据隔离——不在通道内的节点无法访问通道数据。
┌────────────────── Fabric 网络 ──────────────────┐
│ │
│ ┌────────── Channel A ──────────┐ │
│ │ Org1.Peer1, Org2.Peer1 │ │
│ │ 账本 A / 链码 A │ │
│ └──────────────────────────────┘ │
│ │
│ ┌────────── Channel B ──────────┐ │
│ │ Org2.Peer2, Org3.Peer1 │ │
│ │ 账本 B / 链码 B │ │
│ └──────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────┘链码(Chaincode)
链码是 Fabric 中的智能合约,可以用 Go、Java 或 JavaScript 编写。链码通过 Docker 容器隔离运行。
通道与私有数据
私有数据集合(Private Data Collection)
当需要在通道内部分组织间共享敏感数据时,使用私有数据集合。私有数据只在授权节点间通过 gossip 传输,不在通道所有节点间广播。
# collections_config.json
[
{
"name": "privateDetails",
"policy": "OR('Org1MSP.member', 'Org2MSP.member')",
"requiredPeerCount": 0,
"maxPeerCount": 3,
"blockToLive": 0,
"memberOnlyRead": true
}
]// 链码中访问私有数据
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface,
id string, publicData string, privateData string) error {
// 公开数据写入状态
err := ctx.GetStub().PutState(id, []byte(publicData))
if err != nil {
return err
}
// 私有数据写入集合
err = ctx.GetStub().PutPrivateData("privateDetails", id, []byte(privateData))
if err != nil {
return err
}
return nil
}
func (s *SmartContract) GetPrivateData(ctx contractapi.TransactionContextInterface,
id string) (string, error) {
privateBytes, err := ctx.GetStub().GetPrivateData("privateDetails", id)
if err != nil {
return "", fmt.Errorf("failed to get private data: %v", err)
}
return string(privateBytes), nil
}Fabric 链码开发(Go 语言)
项目结构
chaincode/
├── go.mod
├── go.sum
├── main.go
├── chaincode/
│ └── asset.go # 链码逻辑
├── META-INF/
│ └── statedb/
│ └── couchdb/
│ └── indexes/ # CouchDB 索引定义
└── vendor/go.mod
module chaincode
go 1.21
require (
github.com/hyperledger/fabric-chaincode-go v0.0.0-20240112141011-e1a6bd5b2e6c
github.com/hyperledger/fabric-contract-api-go v1.2.2
)资产链码示例
// chaincode/asset.go
package main
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// Asset 定义资产结构
type Asset struct {
ID string `json:"id"`
Owner string `json:"owner"`
Value int `json:"value"`
Color string `json:"color"`
Size int `json:"size"`
AppraisedValue int `json:"appraisedValue"`
}
// SmartContract 合约结构体
type SmartContract struct {
contractapi.Contract
}
// CreateAsset 创建资产
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface,
id string, owner string, value int, color string, size int) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if exists {
return fmt.Errorf("asset %s already exists", id)
}
asset := Asset{
ID: id,
Owner: owner,
Value: value,
Color: color,
Size: size,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// ReadAsset 读取资产
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface,
id string) (*Asset, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return nil, fmt.Errorf("failed to read asset %s: %v", id, err)
}
if assetJSON == nil {
return nil, fmt.Errorf("asset %s does not exist", id)
}
var asset Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
return &asset, nil
}
// UpdateAsset 更新资产
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface,
id string, owner string, value int, color string, size int) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("asset %s does not exist", id)
}
asset := Asset{
ID: id,
Owner: owner,
Value: value,
Color: color,
Size: size,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// DeleteAsset 删除资产
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface,
id string) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("asset %s does not exist", id)
}
return ctx.GetStub().DelState(id)
}
// AssetExists 检查资产是否存在
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface,
id string) (bool, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return false, fmt.Errorf("failed to read asset %s: %v", id, err)
}
return assetJSON != nil, nil
}
// TransferAsset 资产转移
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface,
id string, newOwner string) error {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return err
}
asset.Owner = newOwner
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// GetAllAssets 查询所有资产
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
if err != nil {
return nil, err
}
defer resultsIterator.Close()
var assets []*Asset
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var asset Asset
err = json.Unmarshal(queryResponse.Value, &asset)
if err != nil {
return nil, err
}
assets = append(assets, &asset)
}
return assets, nil
}
// 富查询(需要 CouchDB 状态数据库)
func (s *SmartContract) GetAssetsByOwner(ctx contractapi.TransactionContextInterface,
owner string) ([]*Asset, error) {
queryString := fmt.Sprintf(`{"selector":{"owner":"%s"}}`, owner)
resultsIterator, err := ctx.GetStub().GetQueryResult(queryString)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
var assets []*Asset
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var asset Asset
err = json.Unmarshal(queryResponse.Value, &asset)
if err != nil {
return nil, err
}
assets = append(assets, &asset)
}
return assets, nil
}
// main.go
func main() {
chaincode, err := contractapi.NewChaincode(&SmartContract{})
if err != nil {
fmt.Printf("Error creating chaincode: %s", err.Error())
return
}
if err := chaincode.Start(); err != nil {
fmt.Printf("Error starting chaincode: %s", err.Error())
}
}链码测试
// chaincode/asset_test.go
package main
import (
"encoding/json"
"testing"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// MockStub 模拟存根
type MockStub struct {
shim.ChaincodeStubInterface
mock.Mock
state map[string][]byte
}
func TestCreateAsset(t *testing.T) {
chaincode, ctx := getChaincodeAndCtx()
err := chaincode.CreateAsset(ctx, "asset1", "owner1", 100, "blue", 10)
assert.NoError(t, err)
result, err := chaincode.ReadAsset(ctx, "asset1")
assert.NoError(t, err)
assert.Equal(t, "asset1", result.ID)
assert.Equal(t, "owner1", result.Owner)
}
func TestTransferAsset(t *testing.T) {
chaincode, ctx := getChaincodeAndCtx()
err := chaincode.CreateAsset(ctx, "asset1", "owner1", 100, "blue", 10)
assert.NoError(t, err)
err = chaincode.TransferAsset(ctx, "asset1", "owner2")
assert.NoError(t, err)
result, err := chaincode.ReadAsset(ctx, "asset1")
assert.NoError(t, err)
assert.Equal(t, "owner2", result.Owner)
}
func getChaincodeAndCtx() (*SmartContract, contractapi.TransactionContextInterface) {
// 实际测试中使用 fabric-sdk-go 提供的测试框架
return &SmartContract{}, nil
}Java 链码示例
// AssetContract.java
package org.example;
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.*;
import org.hyperledger.fabric.shim.ChaincodeException;
import org.hyperledger.fabric.shim.ChaincodeStub;
import com.owlike.genson.Genson;
@Contract(name = "AssetContract",
transaction = @Transaction(required = true))
public class AssetContract implements ContractInterface {
private final Genson genson = new Genson();
@Transaction(intent = Transaction.TYPE.SUBMIT)
public Asset createAsset(final Context ctx, final String id,
final String owner, final int value) {
ChaincodeStub stub = ctx.getStub();
String assetState = stub.getStringState(id);
if (!assetState.isEmpty()) {
String errorMessage = String.format("Asset %s already exists", id);
throw new ChaincodeException(errorMessage);
}
Asset asset = new Asset(id, owner, value);
String assetJson = genson.serialize(asset);
stub.putStringState(id, assetJson);
return asset;
}
@Transaction(intent = Transaction.TYPE.EVALUATE)
public Asset readAsset(final Context ctx, final String id) {
ChaincodeStub stub = ctx.getStub();
String assetJson = stub.getStringState(id);
if (assetJson.isEmpty()) {
String errorMessage = String.format("Asset %s does not exist", id);
throw new ChaincodeException(errorMessage);
}
return genson.deserialize(assetJson, Asset.class);
}
}Fabric CA 证书管理
# 启动 Fabric CA 服务器
fabric-ca-server start -b admin:adminpw -p 7054
# 注册新用户
fabric-ca-client register --id.name user1 --id.type client \
--id.affiliation org1.department1 --id.attrs 'role=client:ecert'
# 登记用户(获取证书)
fabric-ca-client enroll -u http://user1:user1pw@localhost:7054 \
-M $FABRIC_CFG_PATH/crypto-config/peerOrganizations/org1.example.com/users/user1/msp
# 获取 TLS 证书
fabric-ca-client enroll -u http://admin:adminpw@localhost:7054 \
--enrollment.profile tls -M $FABRIC_CFG_PATH/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tlsFabric Gateway SDK
// 客户端应用 - 使用 Fabric Gateway SDK
package main
import (
"crypto/x509"
"fmt"
"os"
"path/filepath"
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func main() {
// 1. 加载证书和私钥
certPath := filepath.Join("crypto", "users", "User1@org1.example.com", "msp", "signcerts")
keyPath := filepath.Join("crypto", "users", "User1@org1.example.com", "msp", "keystore")
certPEM, err := os.ReadFile(filepath.Join(certPath, "User1@org1.example.com-cert.pem"))
if err != nil {
panic(err)
}
keyPEM, err := os.ReadFile(filepath.Join(keyPath, "priv_sk"))
if err != nil {
panic(err)
}
// 2. 创建身份和签名器
cert, err := identity.CertificateFromPEM(certPEM)
if err != nil {
panic(err)
}
id, err := identity.NewX509Identity("Org1MSP", cert)
if err != nil {
panic(err)
}
sign, err := identity.NewPrivateKeySigner(keyPEM)
if err != nil {
panic(err)
}
// 3. 建立 gRPC 连接
tlsCertPool := x509.NewCertPool()
tlsCertPEM, err := os.ReadFile(filepath.Join("crypto", "ordererOrganizations", "example.com", "tlsca", "tlsca.example.com-cert.pem"))
if err != nil {
panic(err)
}
tlsCertPool.AppendCertsFromPEM(tlsCertPEM)
conn, err := grpc.Dial(
"localhost:7051",
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(tlsCertPool, "")),
)
if err != nil {
panic(err)
}
defer conn.Close()
// 4. 创建 Gateway 连接
gateway, err := client.Connect(
id,
client.WithSign(sign),
client.WithClientConnection(conn),
client.WithEvaluateTimeout(5*time.Second),
client.WithEndorseTimeout(15*time.Second),
client.WithSubmitTimeout(5*time.Second),
client.WithCommitStatusTimeout(1*time.Minute),
)
if err != nil {
panic(err)
}
defer gateway.Close()
// 5. 调用链码
network := gateway.GetNetwork("mychannel")
contract := network.GetContract("assetcontract")
// 创建资产
result, err := contract.SubmitTransaction("CreateAsset", "asset1", "owner1", "100")
if err != nil {
panic(err)
}
fmt.Printf("CreateAsset result: %s\n", result)
// 读取资产
result, err = contract.EvaluateTransaction("ReadAsset", "asset1")
if err != nil {
panic(err)
}
fmt.Printf("ReadAsset result: %s\n", result)
// 查询所有资产
result, err = contract.EvaluateTransaction("GetAllAssets")
if err != nil {
panic(err)
}
fmt.Printf("GetAllAssets result: %s\n", result)
}联盟链:FISCO BCOS
架构总览
FISCO BCOS 是由微众银行主导研发的国产联盟链平台,具备高性能、高安全、高可用的特点。
┌──────────────────────────────────────────────────┐
│ FISCO BCOS 网络 │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Group 1 │ │ Group 2 │ │
│ │ ┌───┐ ┌───┐ │ │ ┌───┐ ┌───┐ │ │
│ │ │N1 │ │N2 │ │ │ │N3 │ │N4 │ │ │
│ │ └───┘ └───┘ │ │ └───┘ └───┘ │ │
│ │ 共识: PBFT │ │ 共识: Raft │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ WeBASE 管理平台 │ │
│ │ 节点管理 / 合约IDE / 交易审计 │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ WeCross 跨链平台 │ │
│ │ FISCO BCOS <-> Fabric <-> 公链 │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────────────────┘群组架构
FISCO BCOS 引入"群组(Group)"概念,一个网络可以包含多个群组,每个群组有独立的账本和共识机制:
- 一个节点可以加入多个群组,共享节点资源
- 不同群组间数据完全隔离
- 群组支持动态创建和加入
# group.ini 配置示例
[group]
group_id=1
; 共识算法: pbft / raft
consensus_type=pbft
[storage]
storage_security.enable=true
state_db=rocksdb
[tx_pool]
txpool_limit=150000
txpool_obsolete_time=3600
[sync]
idle_wait_ms=200共识机制
PBFT(Practical Byzantine Fault Tolerance)
- 支持拜占庭容错(恶意节点容错)
- 节点数 N,可容忍 (N-1)/3 个拜占庭节点
- 3 阶段提交:Pre-Prepare -> Prepare -> Commit
- 确认延迟低(毫秒级)
Client ──Request──> Primary
│
Pre-Prepare│
▼
┌───────────────┐
│ 所有节点 │
│ Prepare阶段 │
└───────┬───────┘
│
Commit│
▼
┌───────────────┐
│ 所有节点 │
│ Commit阶段 │
└───────┬───────┘
│
┌───────▼───────┐
│ Client收到 │
│ f+1个一致回复 │
└───────────────┘Raft
- 仅支持宕机容错(CFT),不支持拜占庭容错
- 性能更高,适合可信环境
- Leader 选举 + 日志复制
预编译合约
预编译合约是内置在 FISCO BCOS 节点中的高性能合约,用 C++ 实现,在 EVM 外部执行,Gas 消耗更低。
常用预编译合约:
├── Table.sol -> 关系型数据表操作
├── ConsensusPrecompil -> 共识节点管理
├── PermissionPrecompil -> 权限管理
├── ContractLifeCycle -> 合约生命周期管理
└── SystemConfigPrecompil -> 系统配置管理使用 Table 预编译合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
// 引入预编译合约接口
import "./Table.sol";
contract Asset {
event CreateEvent(int256 ret, string id, string data);
event SelectEvent(int256 ret, string data);
// 创建表
function createTable() public returns (int256) {
TableFactory tf = TableFactory(0x1001);
// 表名,主键名,其他字段
int256 result = tf.createTable("t_asset", "id", "owner,value");
return result;
}
// 插入数据
function insert(string memory id, string memory owner, uint256 value) public returns (int256) {
TableFactory tf = TableFactory(0x1001);
Table table = tf.openTable("t_asset");
Entry entry = table.newEntry();
entry.set("id", id);
entry.set("owner", owner);
entry.set("value", string(abi.encodePacked(value)));
int256 count = table.insert(id, entry);
emit CreateEvent(count, id, owner);
return count;
}
// 查询数据
function select(string memory id) public returns (bool, string memory, uint256) {
TableFactory tf = TableFactory(0x1001);
Table table = tf.openTable("t_asset");
Condition condition = table.newCondition();
condition.EQ("id", id);
Entries entries = table.select(id, condition);
if (entries.size() == 0) {
return (false, "", 0);
}
Entry entry = entries.get(0);
string memory owner = entry.getString("owner");
uint256 value = stringToUint(entry.getString("value"));
return (true, owner, value);
}
function stringToUint(string memory s) internal pure returns (uint256) {
bytes memory b = bytes(s);
uint256 result = 0;
for (uint256 i = 0; i < b.length; i++) {
uint256 c = uint256(uint8(b[i]));
if (c >= 48 && c <= 57) {
result = result * 10 + (c - 48);
}
}
return result;
}
}WeBASE 管理平台
WeBASE(WeBank Blockchain Application Software Extension)是 FISCO BCOS 的一站式管理平台。
WeBASE 组件栈:
├── WeBASE-Node-Manager -> 节点管理后端
├── WeBASE-Web -> 管理平台前端
├── WeBASE-Sign -> 签名服务
├── WeBASE-Transaction -> 交易解析服务
├── WeBASE-Front -> 节点前置服务
└── WeBASE-Codegen-Monkey -> 合约代码生成WeBASE 主要功能:
- 节点管理:查看节点状态、共识信息、区块高度
- 合约 IDE:在线编写、编译、部署 Solidity 合约
- 私钥管理:密钥生成、导入、权限控制
- 交易审计:交易查询、解析、回放
- 系统监控:CPU、内存、磁盘、网络监控
# WeBASE 一键部署
git clone https://github.com/WeBankBlockchain/WeBASELargefiles.git
cd WeBASELargefiles
# 修改配置 common.properties
# 执行部署
python3 deploy.py installAll# 通过 WeBASE 部署合约的 API 调用
curl -X POST http://localhost:5001/WeBASE-Node-Manager/contract/deploy \
-H "Content-Type: application/json" \
-d '{
"contractName": "Asset",
"contractSource": "...合约源码...",
"groupId": 1,
"user": "user1"
}'WeCross 跨链方案
WeCross 是 FISCO BCOS 的跨链协作平台,支持异构链之间的互操作。
┌─────────────────────┐
│ WeCross Router │
│ ┌───────────────┐ │
│ │ 路由与验证逻辑 │ │
│ └───────┬───────┘ │
└───────────┼──────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌───────▼───────┐ ┌───────▼───────┐ ┌───────▼───────┐
│ FISCO BCOS │ │ Hyperledger │ │ Ethereum │
│ 跨链代理合约 │ │ Fabric 链码 │ │ 跨链合约 │
└───────────────┘ └───────────────┘ └───────────────┘// WeCross 跨链调用示例
// 部署跨链代理合约后,通过 WeCross Router 调用
// 构造跨链请求
String path = "payment.bcos.group1"; // 目标链路径
String method = "transfer";
String[] args = {"fromUser", "toUser", "100"};
// 通过 WeCross Java SDK 发送跨链交易
TransactionResponse response = WeCrossSDK.sendTransaction(
path,
method,
args
);
System.out.println("Cross-chain result: " + response.getResult());区块链应用场景
供应链溯源
一物一码设计
利用区块链不可篡改的特性,为每个商品赋予唯一的链上身份(数字孪生),记录从生产到消费的全链路信息。
商品溯源流程:
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ 生产 │ ──> │ 加工 │ ──> │ 物流 │ ──> │ 仓储 │ ──> │ 零售 │
│ 环节 │ │ 环节 │ │ 环节 │ │ 环节 │ │ 环节 │
└──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 区块链溯源系统 │
│ 商品ID | 操作 | 操作人 | 时间戳 | 位置 | 证明文件(CID) │
└──────────────────────────────────────────────────────────┘// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract SupplyChain {
enum Stage { Manufactured, Processed, Shipped, Stored, Sold }
struct TraceRecord {
Stage stage;
address operator;
string location;
string documentCID; // IPFS 存储的证明文件
uint256 timestamp;
string remark;
}
struct Product {
string productId; // 商品唯一编码
string name;
address manufacturer;
TraceRecord[] records;
bool exists;
}
mapping(string => Product) public products;
mapping(string => bool) public productExists;
event ProductCreated(string indexed productId, string name, address manufacturer);
event TraceAdded(string indexed productId, Stage stage, address operator);
// 创建商品(生产环节)
function createProduct(
string calldata productId,
string calldata name,
string calldata location,
string calldata documentCID
) external {
require(!productExists[productId], "Product already exists");
Product storage product = products[productId];
product.productId = productId;
product.name = name;
product.manufacturer = msg.sender;
product.exists = true;
product.records.push(TraceRecord({
stage: Stage.Manufactured,
operator: msg.sender,
location: location,
documentCID: documentCID,
timestamp: block.timestamp,
remark: "Product manufactured"
}));
productExists[productId] = true;
emit ProductCreated(productId, name, msg.sender);
}
// 添加追溯记录
function addTrace(
string calldata productId,
Stage stage,
string calldata location,
string calldata documentCID,
string calldata remark
) external {
require(productExists[productId], "Product does not exist");
Product storage product = products[productId];
product.records.push(TraceRecord({
stage: stage,
operator: msg.sender,
location: location,
documentCID: documentCID,
timestamp: block.timestamp,
remark: remark
}));
emit TraceAdded(productId, stage, msg.sender);
}
// 查询商品全链路追溯信息
function getTrace(string calldata productId)
external
view
returns (Product memory)
{
require(productExists[productId], "Product does not exist");
return products[productId];
}
// 验证商品真实性
function verifyProduct(string calldata productId)
external
view
returns (bool valid, uint256 recordCount)
{
require(productExists[productId], "Product does not exist");
Product storage product = products[productId];
return (product.records.length > 0, product.records.length);
}
}前端溯源查询
import { useReadContract } from "wagmi";
function ProductTrace({ productId, contractAddress }: {
productId: string;
contractAddress: `0x${string}`;
}) {
const { data: product } = useReadContract({
address: contractAddress,
abi: SUPPLY_CHAIN_ABI,
functionName: "getTrace",
args: [productId],
});
const stageLabels = ["生产", "加工", "物流", "仓储", "销售"];
if (!product || !product.exists) {
return <div>商品不存在或未上链</div>;
}
return (
<div>
<h3>{product.name}</h3>
<p>商品编码: {product.productId}</p>
<p>生产商: {product.manufacturer}</p>
<h4>全链路追溯记录</h4>
{product.records.map((record, index) => (
<div key={index}>
<span>环节: {stageLabels[record.stage]}</span>
<span>操作人: {record.operator}</span>
<span>位置: {record.location}</span>
<span>时间: {new Date(Number(record.timestamp) * 1000).toLocaleString()}</span>
</div>
))}
</div>
);
}
### 数字身份(DID / 可验证凭证)
#### DID 架构
去中心化身份(Decentralized Identifier, DID)是 W3C 标准的数字身份框架,用户完全控制自己的身份,不依赖任何中心化注册机构。┌─────────────────────────────────────────────────────┐ │ DID 生态系统 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 持有者 │ │ 颁发者 │ │ 验证者 │ │ │ │ (Holder) │ │ (Issuer) │ │ (Verifier)│ │ │ └─────┬────┘ └────┬─────┘ └─────┬────┘ │ │ │ │ │ │ │ ┌─────┴──────────────┴───────────────┴────┐ │ │ │ 区块链 (DID Registry) │ │ │ │ DID Document 存储 / 公钥管理 / 撤销 │ │ │ └──────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────┐ │ │ │ 可验证数据注册表 (Schema) │ │ │ └──────────────────────────────────────────┘ │ └───────────────────────────────────────────────────────┘
**核心角色:**
- **DID 持有者(Holder)**:持有 DID 私钥,控制身份
- **颁发者(Issuer)**:签发可验证凭证(Verifiable Credential, VC)
- **验证者(Verifier)**:验证 VC 的有效性和真实性
#### DID Document 示例
```json
{
"@context": ["https://www.w3.org/ns/did/v1"],
"id": "did:ethr:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"verificationMethod": [
{
"id": "did:ethr:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266#controller",
"type": "EcdsaSecp256k1RecoveryMethod2020",
"controller": "did:ethr:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"blockchainAccountId": "eip155:1:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
}
],
"authentication": [
"did:ethr:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266#controller"
],
"service": [
{
"id": "did:ethr:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266#files",
"type": "EncryptedDataVault",
"serviceEndpoint": "https://vault.example.com"
}
]
}可验证凭证(VC)
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5",
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"issuer": "did:ethr:0x...",
"issuanceDate": "2025-01-01T00:00:00Z",
"credentialSubject": {
"id": "did:ethr:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"degree": {
"type": "BachelorDegree",
"name": "计算机科学与技术",
"university": "清华大学"
}
},
"proof": {
"type": "EcdsaSecp256k1Signature2019",
"created": "2025-01-01T00:00:00Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:ethr:0x...#controller",
"jws": "eyJhbGciOiJFUzI1Nksi..."
}
}链上 DID 合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract DIDRegistry {
struct DIDDocument {
address controller;
bytes32 publicKeyHash;
bool exists;
bool revoked;
}
mapping(string => DIDDocument) public didDocuments;
mapping(address => string) public addressToDID;
event DIDCreated(string indexed did, address indexed controller);
event DIDUpdated(string indexed did);
event DIDRevoked(string indexed did);
function createDID(string calldata did, bytes32 publicKeyHash) external {
require(!didDocuments[did].exists, "DID already exists");
require(bytes(did).length > 0, "DID cannot be empty");
didDocuments[did] = DIDDocument({
controller: msg.sender,
publicKeyHash: publicKeyHash,
exists: true,
revoked: false
});
addressToDID[msg.sender] = did;
emit DIDCreated(did, msg.sender);
}
function resolveDID(string calldata did) external view returns (
address controller,
bytes32 publicKeyHash,
bool exists
) {
DIDDocument storage doc = didDocuments[did];
return (doc.controller, doc.publicKeyHash, doc.exists && !doc.revoked);
}
function revokeDID(string calldata did) external {
require(didDocuments[did].controller == msg.sender, "Not controller");
didDocuments[did].revoked = true;
emit DIDRevoked(did);
}
}版权保护
存证 / 确权流程
版权保护系统通过区块链实现作品的创作时间戳证明和版权归属确权。
作品上链流程:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 创作者 │ │ 作品哈希 │ │ 上链存证 │ │ 生成证书 │
│ 上传文件 │──> │ SHA-256 │──> │ 区块链 │──> │ 电子证书 │
│ │ │ 计算 │ │ 存证 │ │ (DCI码) │
└──────────┘ └──────────┘ └──────────┘ └──────────┘// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract CopyrightRegistry {
struct Copyright {
string title;
address author;
bytes32 contentHash; // 作品内容哈希(SHA-256)
string ipfsCID; // 作品文件 IPFS CID
uint256 timestamp;
string dciCode; // DCI 码(数字版权标识符)
bool registered;
}
mapping(bytes32 => Copyright) public copyrights;
mapping(address => bytes32[]) public authorWorks;
bytes32[] public allWorks;
event CopyrightRegistered(
bytes32 indexed contentHash,
address indexed author,
string title,
uint256 timestamp
);
function registerCopyright(
string calldata title,
bytes32 contentHash,
string calldata ipfsCID,
string calldata dciCode
) external {
require(!copyrights[contentHash].registered, "Already registered");
copyrights[contentHash] = Copyright({
title: title,
author: msg.sender,
contentHash: contentHash,
ipfsCID: ipfsCID,
timestamp: block.timestamp,
dciCode: dciCode,
registered: true
});
authorWorks[msg.sender].push(contentHash);
allWorks.push(contentHash);
emit CopyrightRegistered(contentHash, msg.sender, title, block.timestamp);
}
function verifyCopyright(bytes32 contentHash)
external
view
returns (address author, uint256 timestamp, bool exists)
{
Copyright storage c = copyrights[contentHash];
return (c.author, c.timestamp, c.registered);
}
function getAuthorWorks(address author)
external
view
returns (bytes32[] memory)
{
return authorWorks[author];
}
}政务数据共享
目录链架构
政务数据共享方案利用区块链实现数据目录的发布、发现和授权访问,原始数据不出域、目录与授权信息上链。
┌─────────────────────────────────────────────────────┐
│ 政务数据共享网络 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 数据提供方 │ │ 数据使用方 │ │ 监管机构 │ │
│ │ (市监局) │ │ (税务局) │ │ (大数据局) │ │
│ └─────┬────┘ └─────┬────┘ └─────┬────┘ │
│ │ │ │ │
│ ┌─────┴──────────────┴───────────────┴────┐ │
│ │ 区块链目录链 │ │
│ │ 数据目录 / 授权记录 / 使用审计 │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 数据沙箱 │ │
│ │ 隐私计算 / 联邦学习 / 数据不出域 │ │
│ └──────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract DataSharingGovernance {
enum DataStatus { Created, Approved, Revoked, Expired }
struct DataAsset {
string dataId; // 数据目录 ID
string name; // 数据名称
string description; // 数据描述
address provider; // 提供方
string dataSchemaCID; // 数据 schema(IPFS)
DataStatus status;
uint256 createdAt;
uint256 expiresAt;
}
struct AccessGrant {
address consumer;
string dataId;
uint256 grantedAt;
uint256 expiresAt;
bool active;
}
mapping(string => DataAsset) public dataAssets;
mapping(string => AccessGrant[]) public accessGrants;
mapping(address => bool) public approvedConsumers;
event DataRegistered(string indexed dataId, address provider);
event AccessGranted(string indexed dataId, address consumer);
event AccessRevoked(string indexed dataId, address consumer);
// 注册数据目录
function registerDataAsset(
string calldata dataId,
string calldata name,
string calldata description,
string calldata dataSchemaCID,
uint256 expiresInDays
) external {
require(dataAssets[dataId].status == DataStatus.Created
|| bytes(dataAssets[dataId].dataId).length == 0,
"Data asset already exists");
dataAssets[dataId] = DataAsset({
dataId: dataId,
name: name,
description: description,
provider: msg.sender,
dataSchemaCID: dataSchemaCID,
status: DataStatus.Created,
createdAt: block.timestamp,
expiresAt: block.timestamp + (expiresInDays * 1 days)
});
}
// 授予数据访问权限
function grantAccess(string calldata dataId, address consumer) external {
require(dataAssets[dataId].provider == msg.sender, "Not data provider");
require(approvedConsumers[consumer], "Consumer not approved");
accessGrants[dataId].push(AccessGrant({
consumer: consumer,
dataId: dataId,
grantedAt: block.timestamp,
expiresAt: block.timestamp + 30 days,
active: true
}));
emit AccessGranted(dataId, consumer);
}
// 审计数据使用记录
function logUsageAudit(
string calldata dataId,
address consumer,
bytes32 usageHash
) external {
require(
msg.sender == dataAssets[dataId].provider || msg.sender == consumer,
"Unauthorized"
);
emit UsageAuditLogged(dataId, consumer, usageHash, block.timestamp);
}
event UsageAuditLogged(
string indexed dataId,
address indexed consumer,
bytes32 usageHash,
uint256 timestamp
);
}碳排放交易
MRV 流程
碳排放交易体系中,MRV(Monitoring, Reporting, Verification)是核心业务流程,通过区块链确保碳排放数据的真实性。
MRV 全流程:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Monitoring│ │ Reporting │ │Verification│ │ Trading │
│ 监测 │──>│ 报告 │──>│ 核查 │──>│ 交易 │
│ │ │ │ │ │ │ │
│ IoT设备 │ │ 数据上链 │ │ 第三方 │ │ CCER 交易 │
│ 传感器 │ │ 自动上报 │ │ 核查机构 │ │ 结算 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract CarbonEmissionTrading {
// CCER(国家核证自愿减排量)
struct CCER {
uint256 id;
address owner;
uint256 amount; // 减排量(吨 CO2 当量)
string projectCID; // 项目文档 IPFS CID
uint256 issuedAt;
bool retired;
uint256 retiredAt;
}
struct MRVReport {
string reportId;
address reporter;
uint256 emissionAmount;
string dataCID; // 监测数据 IPFS CID
uint256 reportTime;
address verifier;
bool verified;
uint256 verifiedAt;
}
uint256 public nextCCERId;
mapping(uint256 => CCER) public ccers;
mapping(string => MRVReport) public mrvReports;
mapping(address => uint256) public balances;
event MRVReported(string indexed reportId, address reporter, uint256 amount);
event MRVVerified(string indexed reportId, address verifier, bool passed);
event CCERIssued(uint256 indexed ccerId, address indexed owner, uint256 amount);
event CCERRetired(uint256 indexed ccerId, address indexed owner);
// 提交 MRV 报告
function submitMRVReport(
string calldata reportId,
uint256 emissionAmount,
string calldata dataCID
) external {
mrvReports[reportId] = MRVReport({
reportId: reportId,
reporter: msg.sender,
emissionAmount: emissionAmount,
dataCID: dataCID,
reportTime: block.timestamp,
verifier: address(0),
verified: false,
verifiedAt: 0
});
emit MRVReported(reportId, msg.sender, emissionAmount);
}
// 核查 MRV 报告
function verifyMRV(string calldata reportId, bool passed) external {
MRVReport storage report = mrvReports[reportId];
require(!report.verified, "Already verified");
report.verifier = msg.sender;
report.verified = passed;
report.verifiedAt = block.timestamp;
emit MRVVerified(reportId, msg.sender, passed);
}
// 发放 CCER
function issueCCER(address to, uint256 amount, string calldata projectCID) external {
uint256 ccerId = nextCCERId++;
ccers[ccerId] = CCER({
id: ccerId,
owner: to,
amount: amount,
projectCID: projectCID,
issuedAt: block.timestamp,
retired: false,
retiredAt: 0
});
balances[to] += amount;
emit CCERIssued(ccerId, to, amount);
}
// 交易 CCER
function transferCCER(address to, uint256 ccerId) external {
CCER storage ccer = ccers[ccerId];
require(ccer.owner == msg.sender, "Not owner");
require(!ccer.retired, "Already retired");
ccer.owner = to;
}
// 注销 CCER(用于碳抵消)
function retireCCER(uint256 ccerId) external {
CCER storage ccer = ccers[ccerId];
require(ccer.owner == msg.sender, "Not owner");
require(!ccer.retired, "Already retired");
ccer.retired = true;
ccer.retiredAt = block.timestamp;
balances[msg.sender] -= ccer.amount;
emit CCERRetired(ccerId, msg.sender);
}
}DeFi 基础
DEX 与 AMM
去中心化交易所(DEX)通过自动做市商(AMM)机制实现代币的自动兑换,无需传统订单簿。
Constant Product AMM 公式:x * y = k
其中 x 和 y 是流动性池中两种代币的数量,k 是常数。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IERC20 {
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function transfer(address recipient, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
contract SimpleAMM {
IERC20 public token0;
IERC20 public token1;
uint256 public reserve0;
uint256 public reserve1;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
event Mint(address indexed sender, uint256 amount0, uint256 amount1);
event Burn(address indexed sender, uint256 amount0, uint256 amount1);
event Swap(address indexed sender, uint256 amountIn, uint256 amountOut, address indexed tokenIn);
constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
// 添加流动性
function addLiquidity(uint256 amount0, uint256 amount1) external returns (uint256 shares) {
token0.transferFrom(msg.sender, address(this), amount0);
token1.transferFrom(msg.sender, address(this), amount1);
if (totalSupply == 0) {
shares = _sqrt(amount0 * amount1);
} else {
uint256 share0 = (amount0 * totalSupply) / reserve0;
uint256 share1 = (amount1 * totalSupply) / reserve1;
shares = share0 < share1 ? share0 : share1;
}
_mint(msg.sender, shares);
_updateReserves();
emit Mint(msg.sender, amount0, amount1);
}
// 移除流动性
function removeLiquidity(uint256 shares) external returns (uint256 amount0, uint256 amount1) {
amount0 = (shares * reserve0) / totalSupply;
amount1 = (shares * reserve1) / totalSupply;
_burn(msg.sender, shares);
_updateReserves();
token0.transfer(msg.sender, amount0);
token1.transfer(msg.sender, amount1);
emit Burn(msg.sender, amount0, amount1);
}
// 兑换
function swap(uint256 amountIn, address tokenIn) external returns (uint256 amountOut) {
require(tokenIn == address(token0) || tokenIn == address(token1), "Invalid token");
bool isToken0 = tokenIn == address(token0);
(uint256 reserveIn, uint256 reserveOut) = isToken0
? (reserve0, reserve1)
: (reserve1, reserve0);
uint256 amountInWithFee = (amountIn * 997) / 1000;
amountOut = (reserveOut * amountInWithFee) / (reserveIn + amountInWithFee);
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
IERC20(isToken0 ? address(token1) : address(token0)).transfer(msg.sender, amountOut);
_updateReserves();
emit Swap(msg.sender, amountIn, amountOut, tokenIn);
}
// 计算输出量(只读)
function getAmountOut(uint256 amountIn, address tokenIn) external view returns (uint256) {
(uint256 reserveIn, uint256 reserveOut) = tokenIn == address(token0)
? (reserve0, reserve1)
: (reserve1, reserve0);
uint256 amountInWithFee = (amountIn * 997) / 1000;
return (reserveOut * amountInWithFee) / (reserveIn + amountInWithFee);
}
function _updateReserves() internal {
reserve0 = token0.balanceOf(address(this));
reserve1 = token1.balanceOf(address(this));
}
function _mint(address to, uint256 amount) internal {
balanceOf[to] += amount;
totalSupply += amount;
}
function _burn(address from, uint256 amount) internal {
balanceOf[from] -= amount;
totalSupply -= amount;
}
function _sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
}借贷协议
借贷协议允许用户存入资产赚取利息(Lender),或抵押资产借出其他资产(Borrower)。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract SimpleLending {
IERC20 public collateralToken;
IERC20 public borrowToken;
uint256 public constant LIQUIDATION_THRESHOLD = 80;
uint256 public constant HEALTH_FACTOR = 150;
uint256 public borrowRatePerBlock = 1e9;
struct Position {
uint256 collateralAmount;
uint256 borrowAmount;
uint256 borrowStartBlock;
}
mapping(address => Position) public positions;
uint256 public totalDeposits;
uint256 public totalBorrows;
event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event Borrowed(address indexed user, uint256 amount);
event Repaid(address indexed user, uint256 amount);
event Liquidated(address indexed user, address indexed liquidator, uint256 amount);
constructor(address _collateral, address _borrow) {
collateralToken = IERC20(_collateral);
borrowToken = IERC20(_borrow);
}
function deposit(uint256 amount) external {
collateralToken.transferFrom(msg.sender, address(this), amount);
positions[msg.sender].collateralAmount += amount;
totalDeposits += amount;
emit Deposited(msg.sender, amount);
}
function borrow(uint256 amount) external {
Position storage pos = positions[msg.sender];
require(pos.collateralAmount > 0, "No collateral");
uint256 maxBorrow = getMaxBorrow(pos.collateralAmount);
require(amount <= maxBorrow, "Exceeds max borrow");
pos.borrowAmount += amount;
pos.borrowStartBlock = block.number;
totalBorrows += amount;
borrowToken.transfer(msg.sender, amount);
emit Borrowed(msg.sender, amount);
}
function repay(uint256 amount) external {
Position storage pos = positions[msg.sender];
require(pos.borrowAmount >= amount, "Repay exceeds borrow");
uint256 interest = calculateInterest(pos);
uint256 totalRepay = amount + interest;
borrowToken.transferFrom(msg.sender, address(this), totalRepay);
pos.borrowAmount -= amount;
totalBorrows -= amount;
emit Repaid(msg.sender, amount);
}
function liquidate(address user) external {
Position storage pos = positions[user];
require(isLiquidatable(user), "Position not liquidatable");
uint256 debt = pos.borrowAmount;
uint256 collateralToSeize = (debt * 100) / 50;
pos.borrowAmount = 0;
pos.collateralAmount -= collateralToSeize;
totalBorrows -= debt;
collateralToken.transfer(msg.sender, collateralToSeize);
emit Liquidated(user, msg.sender, collateralToSeize);
}
function isLiquidatable(address user) public view returns (bool) {
Position storage pos = positions[user];
if (pos.borrowAmount == 0) return false;
uint256 maxBorrow = getMaxBorrow(pos.collateralAmount);
uint256 currentBorrow = pos.borrowAmount + calculateInterest(pos);
return currentBorrow > (maxBorrow * LIQUIDATION_THRESHOLD) / 100;
}
function getMaxBorrow(uint256 collateral) internal view returns (uint256) {
return (collateral * 100) / HEALTH_FACTOR;
}
function calculateInterest(Position storage pos) internal view returns (uint256) {
uint256 blocksElapsed = block.number - pos.borrowStartBlock;
return (pos.borrowAmount * borrowRatePerBlock * blocksElapsed) / 1e18;
}
}流动性池(Liquidity Pool)核心指标
| 指标 | 公式 | 说明 |
|---|---|---|
| 恒定乘积 | x * y = k | AMM 核心公式 |
| 滑点 | (amountIn / reserveIn) * price | 大额交易导致的价格偏离 |
| 流动性利用率 | totalBorrows / totalDeposits | 借贷协议资金利用率 |
| 健康因子 | (collateral * price) / (borrow * price) | 借贷健康度,需 > 1 |
| 无常损失 | 2 * sqrt(P_new / P_old) / (1 + P_new / P_old) - 1 | AMM 做市商的潜在损失 |
| 资金费率 | (spotPrice - markPrice) / markPrice | 永续合约多空平衡 |
// 前端计算 AMM 交易参数
function calculateSwap(
reserveIn: bigint,
reserveOut: bigint,
amountIn: bigint,
feeRate: number = 0.003
): { amountOut: bigint; priceImpact: number } {
const amountInWithFee = amountIn * BigInt(Math.floor((1 - feeRate) * 1000)) / 1000n;
const numerator = amountInWithFee * reserveOut;
const denominator = reserveIn + amountInWithFee;
const amountOut = numerator / denominator;
const priceBefore = Number(reserveOut) / Number(reserveIn);
const priceAfter = Number(reserveOut - amountOut) / Number(reserveIn + amountIn);
const priceImpact = Math.abs((priceAfter - priceBefore) / priceBefore) * 100;
return { amountOut, priceImpact };
}
// 计算无常损失
function calculateImpermanentLoss(
initialPrice: number,
currentPrice: number
): number {
const ratio = currentPrice / initialPrice;
const sqrtRatio = Math.sqrt(ratio);
const il = (2 * sqrtRatio) / (1 + ratio) - 1;
return il * 100;
}