项目概述
Dory/Datre 是一个基于推荐人系统的去中心化代币项目,部署在 Arbitrum One 网络上。项目采用三层架构设计:智能合约层、数据索引层和API服务层,实现了完整的推荐人激励生态系统。
核心特性
- 基于 UUPS 代理模式的可升级 ERC-20 代币
- 防循环引用的推荐人系统
- 节点购买与算力分配机制
- 资金冻结与解冻管理
- 链下数据索引与API查询服务
技术栈
- 智能合约: Solidity 0.8.28 + OpenZeppelin
- 数据索引: The Graph + AssemblyScript
- API服务: FastAPI + Python
- 区块链网络: Arbitrum One (L2)
项目地址
- 合约地址:0x6174B1868c61c05Eb520E06ad312E8dd0Af8C553
- 网络:Arbitrum One
- 起始区块:362672657
系统架构
整体架构图
整个系统采用分层架构设计,各组件之间通过事件机制进行通信,确保系统的可扩展性和维护性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 前端应用 │ │ API服务层 │ │ 子图索引 │
│ (Web/Mobile) │◄──►│ (FastAPI) │◄──►│ (The Graph) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ │
▼ ▼
┌─────────────────────────────────────┐
│ Arbitrum One │
│ (智能合约层) │
│ ┌─────────────────────────────┐ │
│ │ DoryContract │ │
│ │ (UUPS代理模式) │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
|
系统架构图展示了从用户界面到区块链网络的完整数据流:
- 前端应用负责用户交互和数据显示
- API服务层提供RESTful接口和业务逻辑处理
- 子图索引负责链上数据的实时同步和查询
- 智能合约层实现核心业务逻辑和状态管理
架构层次说明
智能合约层 (Smart Contract Layer)
负责核心业务逻辑实现,管理代币、推荐人、算力等状态,通过事件机制与外部系统交互,支持 UUPS 代理模式升级。
数据索引层 (Data Indexing Layer)
监听智能合约事件,构建链下数据模型,提供 GraphQL 查询接口,支持复杂关系查询。
API服务层 (API Service Layer)
封装子图查询逻辑,提供 RESTful API 接口,处理数据格式转换,实现业务逻辑封装。
智能合约详解
合约目录结构
项目采用模块化的合约组织结构,为后续添加更多合约做好准备。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
contracts/
├── datre-token/ # Datre Token 合约模块
│ ├── contracts/ # 智能合约源码
│ │ └── Datre.sol # 主合约文件
│ ├── test/ # 测试文件
│ │ └── Datre.test.js # 合约测试
│ ├── scripts/ # 部署脚本
│ │ └── deploy.js # 部署脚本
│ ├── artifacts/ # 编译产物
│ ├── cache/ # 编译缓存
│ ├── hardhat.config.js # Hardhat 配置
│ ├── package.json # 合约依赖
│ └── README.md # 项目说明
├── datre-hook/ # Uniswap v4 Hook 合约模块
│ ├── contracts/ # Hook 合约源码
│ ├── test/ # 测试用例
│ ├── scripts/ # 部署脚本
│ ├── hardhat.config.js # Hardhat 配置
│ └── package.json # Hook 依赖
└── datre-distributor/ # 代币分发合约模块
├── contracts/ # 分发合约源码
├── test/ # 测试用例
├── scripts/ # 部署脚本
├── hardhat.config.js # Hardhat 配置
└── package.json # 分发合约依赖
|
** 合约模块说明
三个合约项目相互配合,构成完整的 Datre 生态系统:
- datre-token/: 发币合约,基于 OpenZeppelin 的可升级 ERC20 代币,提供代币铸造功能
- datre-hook/: 交易合约,Uniswap v4 Hook 实现特殊的交易规则(5% 手续费 + 买入销毁)
- datre-distributor/: 分发合约,基于 Merkle Root 验证的按轮次分发系统,调用 datre-token 的 mint 功能
- 扩展性: 后续可添加更多合约模块(如治理合约、质押合约等)
Distributor 合约详情
Distributor 是一个基于 Merkle Root 验证的代币分发合约,支持按轮次(epoch)分发 Datre Token。
** 核心功能
- Epoch 管理: 支持多轮次分发,每轮次独立的 Merkle Root
- Merkle 验证: 使用 Merkle Proof 验证用户领取资格
- 位图防重: 使用位图防止重复领取
- 时间窗口: 支持设置领取时间窗口
- 限额控制: 支持轮次限额和日限额
- 暂停机制: 支持紧急暂停和恢复
- 可升级: 基于 UUPS 代理模式,支持合约升级
- 域隔离: 防止跨链、跨代币、跨合约的重放攻击
** 安全特性
- 域隔离: 叶子节点包含代币地址、链ID、分发器地址,防止重放攻击
- 权限控制: 只有 owner 可以设置 Merkle Root 和暂停合约
- 重入保护: 所有外部调用都使用 nonReentrant 修饰符
- 限额保护: 支持轮次和日限额,防止超发
- 紧急撤权: 支持紧急暂停和代币权限撤销
** 与 datre-token 发币合约的集成
- 调用 datre-token 的 mint 函数直接铸造代币给用户
- 需要 datre-token 提供 minter 权限管理(setMinter/isMinter)
- 支持紧急撤权:同时撤销 minter 权限和暂停分发器
- 升级时保持权限不变,确保连续性
- 不持有大额库存,按需铸造,降低安全风险
合约基本信息
合约采用UUPS代理模式,支持升级和扩展功能。
1
2
3
4
5
6
7
|
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract DoryContract is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
string public constant VERSION = "1.0.0";
// ... 其他代码
}
|
DatreHook 合约
DatreHook 是为 DATRE 代币设计的 Uniswap v4 Hook,实现了特殊的交易规则。
* 交易规则
- 卖出 DATRE→USDC: 从 DATRE 输入中扣除 5% 手续费给 feeRecipient,剩余 95% 进入池子成交
- 买入 DATRE←USDC: 从 USDC 输入中扣除 5% 手续费给 feeRecipient,剩余 95% 进入池子成交,产出的 DATRE 全额销毁到黑洞地址,并按 datreOut 给买家铸造算力
* 核心特性
- 规则在 Hook 内强制执行,任何 Router/聚合器都无法绕过
- 支持 EIP-55 校验和地址格式
- 完整的权限控制和事件记录
- 支持 CREATE2 部署以满足 Hook 权限位要求
* 合约接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
contract SimpleDatreHook is Ownable {
uint256 public constant BPS_DENOM = 10000;
uint256 public feeBps = 500; // 5%
address public immutable feeRecipient;
address public immutable blackhole;
address public immutable power;
IERC20 public immutable datre;
IERC20 public immutable usdc;
function simulateSellDatre(uint256 datreAmount) external;
function simulateBuyDatre(uint256 usdcAmount, address user) external;
function setFeeBps(uint256 _feeBps) external onlyOwner;
}
|
* 部署说明
- 测试网部署: 直接使用 deploy.js 脚本部署,enforceHookPerms 设置为 false
- 主网部署: 使用 findSalt.js 脚本找到满足 Hook 权限位的 CREATE2 地址,然后使用该盐值部署
- 参数配置: feeRecipient(手续费接收地址)、blackhole(销毁地址)、power(算力合约地址)
* 事件说明
- FeeTaken: 手续费收取事件,记录收取的代币和数量
- BurnedOnBuy: DATRE 销毁事件,记录销毁的数量
- PowerMinted: 算力铸造事件,记录用户地址和铸造的算力数量
- FeeUpdated: 手续费比例更新事件
- FeeRecipientUpdated: 手续费接收地址更新事件
- BlackholeUpdated: 销毁地址更新事件
- PowerUpdated: 算力合约地址更新事件
- PoolTokenOrderSet: 池子代币顺序设置事件
* 核心功能
- beforeSwap: 在交易前收取 5% 手续费,支持 DATRE→USDC 和 USDC→DATRE 两个方向
- afterSwap: 在买入 DATRE 后销毁所有产出的 DATRE 并铸造算力给用户
- 池子代币顺序检测: 自动检测并记录池子中 DATRE 和 USDC 的顺序
- hookData 解析: 从 hookData 中提取真实用户地址,支持聚合器路径
* 安全考虑
- Hook 规则在合约内强制执行,无法被外部绕过
- 支持 EIP-55 校验和地址格式,防止地址混淆攻击
- 完整的权限控制,只有 owner 可以修改关键参数
- 事件记录完整,便于审计和追踪
- 支持 CREATE2 部署以满足 Hook 权限位要求
核心数据结构
UserInfo 结构
UserInfo结构定义了用户的基本信息,包括推荐人关系、算力值和时间戳等。
1
2
3
4
5
6
7
8
9
10
|
struct UserInfo {
address referrer; // 推荐人地址
uint256 power; // 算力值
uint256 joinTime; // 加入时间
uint256 updateTime; // 最后更新时间
uint256 reserved1; // 保留字段1
uint256 reserved2; // 保留字段2
bytes32 reserved3; // 保留字段3
bool reserved4; // 保留字段4
}
|
NodeConfig 结构
NodeConfig结构定义了节点的配置信息,包括价格、算力和库存状态。
1
2
3
4
5
6
|
struct NodeConfig {
uint256 price; // 节点价格 (USDT, 6位小数)
uint256 power; // 节点算力
uint256 remaining; // 剩余数量
bool isActive; // 是否开放购买
}
|
FreezeRecord 结构
FreezeRecord结构记录了资金冻结的详细信息,支持临时和永久冻结。
1
2
3
4
5
6
7
|
struct FreezeRecord {
uint256 amount; // 冻结金额
uint256 timestamp; // 冻结时间
uint256 expireTime; // 到期时间 (0表示永久冻结)
string reason; // 冻结原因
bool isActive; // 是否激活状态
}
|
核心功能模块
推荐人系统
-
设置推荐人
设置推荐人函数实现了防循环引用的推荐关系建立。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
function setReferrer(address referrer) external {
require(referrer != address(0), "Referrer cannot be zero address");
require(referrer != msg.sender, "Cannot refer yourself");
require(_userInfos[msg.sender].referrer == address(0), "Referrer already set");
require(!_hasCircularReference(msg.sender, referrer), "Circular reference detected");
_userInfos[msg.sender] = UserInfo({
referrer: referrer,
power: 0,
joinTime: block.timestamp,
updateTime: block.timestamp,
reserved1: 0,
reserved2: 0,
reserved3: bytes32(0),
reserved4: false
});
emit ReferrerSet(msg.sender, referrer, block.timestamp);
emit ReferralRelationshipEstablished(referrer, msg.sender, block.timestamp);
}
|
-
防循环引用检查
防循环引用检查函数确保推荐关系不会形成闭环。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function _hasCircularReference(address account, address newReferrer) private view returns (bool) {
address current = newReferrer;
uint256 maxDepth = 100;
for (uint256 i = 0; i < maxDepth; i++) {
address currentReferrer = _userInfos[current].referrer;
if (currentReferrer == address(0)) {
break;
}
if (currentReferrer == account) {
return true;
}
current = currentReferrer;
}
return false;
}
|
节点购买系统
-
节点配置初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function _initializeNodeConfigs() private {
// 大节点配置: 5000 USDT, 算力5000, 剩余100个
_nodeConfigs[NodeType.BigNode] = NodeConfig({
price: 5000 * 10**6, // 5000 USDT (6位小数)
power: 5000, // 算力5000
remaining: 100, // 剩余100个
isActive: true // 开放购买
});
// 小节点配置: 1000 USDT, 算力1000, 剩余300个
_nodeConfigs[NodeType.SmallNode] = NodeConfig({
price: 1000 * 10**6, // 1000 USDT (6位小数)
power: 1000, // 算力1000
remaining: 300, // 剩余300个
isActive: true // 开放购买
});
}
|
-
节点购买函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function purchaseNode(NodeType nodeType) external {
_purchaseNodeInternal(msg.sender, nodeType);
}
function purchaseNodeWithPermit(
NodeType nodeType,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
// 使用 permit 机制,无需提前 approve
IERC20Permit(usdtToken).permit(msg.sender, address(this), _nodeConfigs[nodeType].price, deadline, v, r, s);
_purchaseNodeInternal(msg.sender, nodeType);
}
|
资金冻结系统
-
冻结资金
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
function freezeFunds(
address account,
uint256 amount,
uint256 duration,
string memory reason
) external onlyOwner {
require(account != address(0), "Account cannot be zero address");
require(amount > 0, "Amount must be greater than 0");
require(duration > 0, "Duration must be greater than 0");
uint256 freezeId = _nextFreezeId[account]++;
uint256 expireTime = duration == type(uint256).max ? 0 : block.timestamp + duration;
_freezeRecords[account][freezeId] = FreezeRecord({
amount: amount,
timestamp: block.timestamp,
expireTime: expireTime,
reason: reason,
isActive: true
});
_totalFrozenAmount[account] += amount;
emit FundsFreezed(account, freezeId, amount, reason);
}
|
-
自动解冻检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
function checkAndUnfreezeExpired(address account) public {
uint256 totalUnfrozen = 0;
for (uint256 i = 0; i < _nextFreezeId[account]; i++) {
FreezeRecord storage record = _freezeRecords[account][i];
if (record.isActive && record.expireTime > 0 && record.expireTime <= block.timestamp) {
record.isActive = false;
totalUnfrozen += record.amount;
emit FundsAutoUnfreezed(account, i, record.amount);
}
}
if (totalUnfrozen > 0) {
_totalFrozenAmount[account] -= totalUnfrozen;
}
}
|
事件系统
推荐人相关事件
1
2
3
|
event ReferrerSet(address indexed account, address indexed referrer, uint256 timestamp);
event ReferrerModified(address indexed account, address indexed oldReferrer, address indexed newReferrer, uint256 timestamp);
event ReferralRelationshipEstablished(address indexed referrer, address indexed referee, uint256 timestamp);
|
算力相关事件
1
|
event PowerUpdated(address indexed account, uint256 oldPower, uint256 newPower, uint256 timestamp);
|
节点相关事件
1
2
|
event NodePurchased(address indexed buyer, NodeType indexed nodeType, uint256 usdtAmount, uint256 powerGained, uint256 timestamp);
event NodeConfigUpdated(NodeType indexed nodeType, uint256 price, uint256 power, uint256 remaining, bool isActive);
|
冻结相关事件
1
2
3
|
event FundsFreezed(address indexed account, uint256 indexed freezeId, uint256 amount, string reason);
event FundsUnfreezed(address indexed account, uint256 indexed freezeId, uint256 amount);
event FundsAutoUnfreezed(address indexed account, uint256 indexed freezeId, uint256 amount);
|
权限控制
所有者权限
- 代币铸造与销毁
- 用户算力设置
- 推荐人修改
- 资金冻结与解冻
- 节点配置更新
- 合约升级
用户权限
- 设置推荐人(一次性)
- 购买节点
- 查询个人信息
- 查询推荐路径
Gas 优化策略
- **移除链上统计**:推荐人数量等统计信息通过事件在链下构建
- **批量操作**:支持批量设置算力等操作
- **存储优化**:使用紧凑的数据结构,预留扩展字段
- **事件驱动**:通过事件机制减少链上存储和计算
子图索引系统
子图配置
基本信息
- 名称:dory-subgraph-v0.0.7
- 版本:0.0.7
- 网络:arbitrum-one
- 合约地址:0x6174B1868c61c05Eb520E06ad312E8dd0Af8C553
- 起始区块:362672657
数据源配置
1
2
3
4
5
6
7
8
|
dataSources:
- kind: ethereum
name: DoryContract
network: arbitrum-one
source:
address: "0x6174B1868c61c05Eb520E06ad312E8dd0Af8C553"
abi: DoryContract
startBlock: 362672657
|
数据模型设计
User 实体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
type User @entity {
id: ID! # 用户地址
address: Bytes! # 用户地址
power: BigInt! # 用户算力
referreeCount: BigInt! # 直接被推荐人数量
bindingTime: BigInt! # 绑定时间
# 推荐关系
referrees: [User!]! @derivedFrom(field: "referrer")
referrer: User
# 推荐关系记录
referralRelationships: [ReferralRelationship!]! @derivedFrom(field: "referee")
referrerRelationships: [ReferralRelationship!]! @derivedFrom(field: "referrer")
}
|
ReferralRelationship 实体
1
2
3
4
5
6
7
|
type ReferralRelationship @entity {
id: ID! # 推荐关系ID
referrer: User! # 推荐人
referee: User! # 被推荐人
timestamp: BigInt! # 绑定时间
isActive: Boolean! # 是否活跃
}
|
事件处理器
推荐人设置处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
export function handleReferrerSet(event: ReferrerSet): void {
let user = getOrCreateUser(event.params.account);
let referrer = getOrCreateUser(event.params.referrer);
// 检查是否已经存在推荐关系,避免重复计算
if (user.referrer == null) {
// 设置推荐人和绑定时间
user.referrer = referrer.id;
user.bindingTime = event.params.timestamp;
user.save();
// 更新推荐人的直接推荐人数量
updateReferreeCount(referrer, true);
}
// 创建推荐关系记录
let relationship = new ReferralRelationship(
event.transaction.hash.toHexString() + "-" + event.logIndex.toString()
);
relationship.referrer = referrer.id;
relationship.referee = user.id;
relationship.timestamp = event.params.timestamp;
relationship.isActive = true;
relationship.save();
}
|
推荐人修改处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
export function handleReferrerModified(event: ReferrerModified): void {
let user = getOrCreateUser(event.params.account);
let oldReferrer: User | null = null;
let newReferrer: User | null = null;
// 获取旧推荐人
if (user.referrer != null) {
oldReferrer = User.load(user.referrer!);
}
// 设置新推荐人
if (!event.params.newReferrer.equals(Address.zero())) {
newReferrer = getOrCreateUser(event.params.newReferrer);
user.referrer = newReferrer.id;
} else {
user.referrer = null;
}
user.save();
// 创建新的推荐关系(如果新推荐人不为空)
if (!event.params.newReferrer.equals(Address.zero())) {
let relationship = new ReferralRelationship(
event.transaction.hash.toHexString() + "-" + event.logIndex.toString()
);
relationship.referrer = newReferrer!.id;
relationship.referee = user.id;
relationship.timestamp = event.params.timestamp;
relationship.isActive = true;
relationship.save();
}
// 更新旧推荐人的直接推荐人数量
if (oldReferrer) {
updateReferreeCount(oldReferrer, false);
}
// 更新新推荐人的直接推荐人数量
if (newReferrer) {
updateReferreeCount(newReferrer, true);
}
}
|
算力更新处理
1
2
3
4
5
|
export function handlePowerUpdated(event: PowerUpdated): void {
let user = getOrCreateUser(event.params.account);
user.power = event.params.newPower;
user.save();
}
|
工具函数
用户创建与获取
1
2
3
4
5
6
7
8
9
10
11
12
|
function getOrCreateUser(address: Address): User {
let user = User.load(address.toHexString());
if (!user) {
user = new User(address.toHexString());
user.address = address;
user.power = BigInt.fromI32(0);
user.referreeCount = BigInt.fromI32(0);
user.bindingTime = BigInt.fromI32(0);
user.save();
}
return user;
}
|
推荐人数量更新
1
2
3
4
5
6
7
8
|
function updateReferreeCount(user: User, increment: boolean): void {
if (increment) {
user.referreeCount = user.referreeCount.plus(BigInt.fromI32(1));
} else {
user.referreeCount = user.referreeCount.minus(BigInt.fromI32(1));
}
user.save();
}
|
数据统计与验证
推荐人数量重新计算
1
2
3
4
5
|
function recalculateReferreeCount(user: User): void {
// 重置计数
user.referreeCount = BigInt.fromI32(0);
user.save();
}
|
推荐人数量验证
1
2
3
4
5
|
function validateReferreeCount(user: User): void {
// 这里可以添加验证逻辑
// 如果发现数量不匹配,可以重新计算
user.save();
}
|
API 服务系统
技术架构
框架选择
- **Web框架**:FastAPI
- **异步支持**:httpx (异步HTTP客户端)
- **数据验证**:Pydantic
- **区块链交互**:Web3.py
- **环境配置**:python-dotenv
项目结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
datre/
├── contracts/ # 智能合约目录
│ ├── datre-token/ # Datre Token 合约
│ │ ├── Datre.sol # 主合约文件 (ERC-20 + 推荐系统)
│ │ ├── artifacts/ # 编译产物
│ │ ├── hardhat.config.js # Hardhat 配置
│ │ └── package.json # 合约依赖
│ └── datre-hook/ # Uniswap v4 Hook 合约
│ ├── contracts/ # Hook 合约源码
│ ├── test/ # 测试用例
│ ├── scripts/ # 部署脚本
│ ├── hardhat.config.js # Hardhat 配置
│ └── package.json # Hook 依赖
├── ledger/ # 数据同步服务
│ ├── program/ # 同步程序
│ │ ├── core/ # 核心模块
│ │ ├── sync/ # 同步模块
│ │ └── run.py # 启动脚本
│ ├── config/ # 配置文件
│ └── database/ # 数据库初始化
├── service/ # API 服务
│ ├── program/ # API 程序
│ │ ├── app.py # FastAPI 应用
│ │ ├── db.py # 数据库操作
│ │ └── run.py # 启动脚本
│ └── config/ # 配置文件
├── subgraph/ # 子图索引
│ ├── src/ # 映射源码
│ ├── abis/ # 合约 ABI
│ └── schema.graphql # 数据模式
└── README.org # 项目文档
|
API 接口设计
用户信息查询接口
1
2
3
4
5
6
7
8
9
10
11
|
@app.get("/v1/user", response_model=UserResponse)
async def get_user(address: str = Query(..., description="钱包地址")):
"""
获取用户信息
Args:
address: 钱包地址(需要小写)
Returns:
用户信息,包括推荐人列表和绑定时间
"""
|
请求参数
- address: 钱包地址(字符串,需要小写格式)
响应模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class UserResponse(BaseModel):
code: int = 0
msg: str = "success"
data: Optional[dict] = None
class User(BaseModel):
id: str
address: str
power: float
level: int = 0
smallCommunityPower: float = 0.0 # 小区算力
communityPower: float = 0.0 # 社区算力
maxCommunityPower: float = 0.0 # 大区算力
powerDecay: int = 0 # 算力衰减
expectedReward: float = 0.0 # 预计收益
referreeCount: int
referrees: List[Referree]
class Referree(BaseModel):
id: str
address: str
power: float
bindingTime: int
communityPower: float # 社区算力
|
核心业务逻辑
地址处理与验证
1
2
3
4
5
6
7
8
9
10
|
# 处理地址格式
address = address.lower()
# 添加0x前缀(如果没有)
if not address.startswith('0x'):
address = '0x' + address
# 验证地址格式
if not Web3.is_address(address):
return UserResponse(code=0, msg="无效的钱包地址格式", data={})
|
GraphQL 查询构建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
query = """
{
user(id: "%s") {
id
address
power
referreeCount
referrees(first: 100) {
id
address
power
bindingTime
}
}
}
""" % address
|
数据格式转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 转换数据格式
referrees = []
for ref in user_data.get("referrees", []):
ref_power = float(ref["power"])
referrees.append(Referree(
id=Web3.to_checksum_address(ref["id"]),
address=Web3.to_checksum_address(ref["address"]),
power=ref_power,
bindingTime=int(ref["bindingTime"]),
communityPower=ref_power # 社区算力等于本人算力
))
# 按照绑定时间倒序排列
referrees.sort(key=lambda x: x.bindingTime, reverse=True)
|
配置管理
环境变量配置
1
2
3
4
5
6
|
# 加载环境变量
load_dotenv()
# 配置
SUBGRAPH_URL = os.getenv("SUBGRAPH_URL", "https://api.studio.thegraph.com/query/117650/dory/version/latest")
API_KEY = os.getenv("API_KEY", "3775070ab8c4f893227a8b8e42f00818")
|
CORS 配置
1
2
3
4
5
6
7
8
|
# 添加 CORS 中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
|
错误处理
HTTP 异常处理
1
2
3
4
5
|
if response.status_code != 200:
raise HTTPException(
status_code=500,
detail=f"Subgraph request failed: {response.status_code}"
)
|
GraphQL 错误处理
1
2
3
4
5
6
7
|
# 检查是否有错误
if "errors" in data:
error_msg = data["errors"][0]["message"] if data["errors"] else "Unknown error"
raise HTTPException(
status_code=400,
detail=f"GraphQL error: {error_msg}"
)
|
用户不存在处理
1
2
3
4
5
6
7
|
# 检查用户是否存在
if not data.get("data", {}).get("user"):
return UserResponse(
code=0,
msg="error",
data={}
)
|
部署配置
服务器配置
1
2
3
|
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8911)
|
依赖管理
1
2
3
4
5
6
7
|
# requirements.txt
fastapi
uvicorn
httpx
pydantic
python-dotenv
web3
|
数据流与事件处理
完整数据流
1
2
3
4
5
6
7
8
|
用户操作 → 智能合约 → 事件触发 → 子图监听 → 数据索引 → API查询 → 前端展示
│ │ │ │ │ │
│ │ │ │ │ └── 用户界面
│ │ │ │ └── 业务逻辑处理
│ │ │ └── GraphQL查询
│ │ └── 实体创建/更新
│ └── 状态变更
└── 交易提交
|
事件处理流程
推荐人设置流程
- 用户调用 `setReferrer(address referrer)`
- 合约验证参数并设置推荐关系
- 触发 `ReferrerSet` 和 `ReferralRelationshipEstablished` 事件
- 子图监听事件并调用相应处理器
- 创建/更新 User 实体和 ReferralRelationship 实体
- 更新推荐人数量统计
节点购买流程
- 用户调用 `purchaseNode(NodeType nodeType)`
- 合约验证USDT余额和节点配置
- 转移USDT并分配算力
- 触发 `NodePurchased` 事件
- 子图监听事件并更新用户算力
算力更新流程
- 管理员调用 `setPower(address account, uint256 newPower)`
- 合约更新用户算力
- 触发 `PowerUpdated` 事件
- 子图监听事件并更新用户实体
数据一致性保证
事件顺序性
- 使用 `event.transaction.hash + event.logIndex` 作为关系ID
- 确保事件处理的顺序性和唯一性
状态同步
- 实时监听合约事件
- 自动更新相关实体状态
- 支持数据重新同步和修复
错误恢复
- 提供数据验证和修复函数
- 支持手动重新计算统计数据
- 异常情况的日志记录和告警
部署与运维
智能合约部署
部署环境要求
- Solidity 编译器版本:^0.8.28
- OpenZeppelin 合约库
- 支持 UUPS 代理的网络
- 足够的 Gas 费用
部署步骤
- 编译合约代码
- 部署代理合约
- 初始化合约参数
- 验证合约功能
- 设置权限和配置
升级流程
- 部署新的实现合约
- 调用 `upgradeToAndCall` 函数
- 验证升级后的功能
- 更新前端配置
子图部署
环境准备
- 安装 Graph CLI
- 配置 Graph Studio 账户
- 准备 ABI 文件
部署命令
1
2
3
4
5
6
7
8
9
10
11
12
|
# 代码生成
graph codegen
# 构建子图
graph build
# 部署到 Graph Studio
graph deploy dory
# 本地测试
graph create --node http://localhost:8020 dory
graph deploy --node http://localhost:8020 --ipfs http://localhost:5001 dory
|
配置更新
- 修改 `subgraph.yaml` 配置
- 更新事件处理器
- 重新部署子图
API 服务部署
环境要求
部署步骤
- 创建虚拟环境
- 安装依赖包
- 配置环境变量
- 启动服务
服务管理
1
2
3
4
5
6
7
8
9
10
11
|
# 安装依赖
pip install -r requirements.txt
# 启动服务
python main.py
# 后台运行
nohup python main.py > app.log 2>&1 &
# 停止服务
pkill -f "python main.py"
|
监控与维护
系统监控
- 合约事件监听状态
- 子图同步状态
- API 服务响应时间
- 数据库连接状态
日志管理
- 合约调用日志
- 子图处理日志
- API 访问日志
- 错误和异常日志
性能优化
- 数据库查询优化
- 缓存策略实施
- 负载均衡配置
- 资源使用监控
安全考虑
智能合约安全
权限控制
输入验证
重入攻击防护
- 使用 OpenZeppelin 的安全库
- 状态变更顺序控制
- 外部调用安全处理
数据安全
访问控制
数据完整性
隐私保护
网络安全
传输安全
- HTTPS 协议使用
- API 密钥管理
- 请求频率限制
防护措施
- DDoS 攻击防护
- SQL 注入防护
- XSS 攻击防护
测试策略
智能合约测试
单元测试
集成测试
安全测试
子图测试
功能测试
性能测试
API 测试
接口测试
负载测试
性能优化
智能合约优化
Gas 优化
计算优化
子图优化
查询优化
存储优化
API 优化
响应优化
资源优化
扩展性设计
水平扩展
负载均衡
数据库扩展
垂直扩展
资源升级
架构优化
功能扩展
新特性添加
集成扩展
故障处理与恢复
常见故障类型
智能合约故障
子图故障
API 服务故障
故障检测
监控告警
日志分析
故障恢复
自动恢复
手动恢复
灾难恢复
备份策略
恢复流程
开发指南
开发环境搭建
环境要求
- Node.js 16+
- Python 3.8+
- Solidity 0.8.28+
- Git
合约开发环境
1
2
3
4
5
6
7
8
9
10
11
|
# 进入合约目录
cd contracts/datre-token
# 安装合约依赖
npm install
# 编译合约
npm run compile
# 运行测试
npm run test
|
DatreHook 开发环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 进入 Hook 合约目录
cd contracts/datre-hook
# 安装依赖
npm install
# 编译合约
npm run compile
# 运行测试
npm test
# 部署合约
npm run deploy
# 查找 CREATE2 盐(用于主网部署)
npm run find-salt
|
添加新合约模块
1
2
3
4
5
6
7
8
9
10
11
12
|
# 创建新合约目录
mkdir contracts/new-contract
# 初始化新合约
cd contracts/new-contract
npm init -y
# 添加 Hardhat 依赖
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
# 创建 hardhat.config.js 配置文件
# 添加合约源码和测试文件
|
工具安装
1
2
3
4
5
6
7
8
|
# 安装 Node.js 依赖
npm install -g @graphprotocol/graph-cli
# 安装 Python 依赖
pip install fastapi uvicorn httpx pydantic python-dotenv web3
# 安装 Solidity 编译器
npm install -g solc
|
代码规范
Solidity 规范
- 使用 SPDX 许可证标识
- 遵循 Solidity 风格指南
- 添加完整的 NatSpec 注释
- 使用安全的 OpenZeppelin 库
TypeScript 规范
- 使用 TypeScript 严格模式
- 遵循 ESLint 规则
- 添加类型注解
- 使用 async/await 语法
Python 规范
- 遵循 PEP 8 代码风格
- 使用类型注解
- 添加文档字符串
- 使用虚拟环境
测试规范
测试覆盖率
- 智能合约测试覆盖率 > 90%
- 子图处理器测试覆盖率 > 80%
- API 接口测试覆盖率 > 85%
测试类型
部署规范
环境管理
版本管理
常见问题与解决方案
智能合约问题
推荐人设置失败
问题描述**:用户无法设置推荐人
可能原因**:
- 推荐人地址为零地址
- 用户已设置过推荐人
- 存在循环引用
- Gas 费用不足
解决方案**:
- 检查推荐人地址格式
- 确认用户未设置过推荐人
- 检查推荐链是否存在循环
- 增加 Gas 费用
节点购买失败
问题描述**:用户无法购买节点
可能原因**:
- USDT 余额不足
- 节点数量已售罄
- 节点购买功能未开启
- 权限不足
解决方案**:
- 检查 USDT 余额
- 确认节点库存状态
- 检查节点配置状态
- 联系管理员
子图问题
数据同步失败
问题描述**:子图数据与链上数据不一致
可能原因**:
- 子图服务中断
- 事件处理失败
- 网络连接问题
- 配置错误
解决方案**:
- 检查子图服务状态
- 查看事件处理日志
- 检查网络连接
- 验证配置参数
查询响应慢
问题描述**:GraphQL 查询响应时间过长
可能原因**:
解决方案**:
- 优化查询语句
- 添加适当索引
- 实施分页查询
- 增加计算资源
API 问题
服务无响应
问题描述**:API 服务无法访问
可能原因**:
- 服务进程崩溃
- 端口被占用
- 防火墙阻止
- 依赖服务异常
解决方案**:
- 检查服务进程状态
- 确认端口占用情况
- 检查防火墙配置
- 验证依赖服务
数据格式错误
问题描述**:API 返回数据格式不正确
可能原因**:
- 子图数据异常
- 数据转换错误
- 模型定义不匹配
- 编码问题
解决方案**:
- 检查子图数据
- 验证数据转换逻辑
- 更新数据模型
- 检查编码设置
未来规划
功能扩展
多链支持
- 支持以太坊主网
- 支持其他 L2 网络
- 跨链数据同步
- 多链资产管理
高级推荐系统
- 多级推荐奖励
- 动态奖励算法
- 推荐人等级系统
- 社区治理机制
治理功能
技术升级
性能优化
- 批量处理优化
- 缓存机制优化
- 数据库查询优化
- 网络传输优化
安全增强
- 多重签名支持
- 时间锁机制
- 紧急暂停功能
- 安全审计增强
可扩展性
生态建设
开发者工具
- SDK 开发
- API 文档完善
- 开发模板
- 测试工具
社区建设
合作伙伴
- 钱包集成
- DEX 集成
- 数据分析平台
- 安全审计机构
附录
相关链接
官方资源
- 项目官网:[待补充]
- 技术文档:[待补充]
- GitHub 仓库:[待补充]
- 社区论坛:[待补充]
第三方资源
术语表
区块链术语
- **L2 (Layer 2)**:第二层扩展解决方案,如 Arbitrum
- **Gas**:以太坊网络交易费用
- **智能合约**:在区块链上自动执行的程序
- **事件 (Event)**:智能合约发出的日志信息
推荐系统术语
- **推荐人 (Referrer)**:邀请其他用户加入的用户
- **被推荐人 (Referee)**:被邀请加入的用户
- **推荐链 (Referral Chain)**:推荐关系的层级结构
- **算力 (Power)**:用户在系统中的权重值
技术术语
- **子图 (Subgraph)**:The Graph 协议的数据索引
- **UUPS 代理**:可升级的智能合约模式
- **GraphQL**:数据查询语言
- **API**:应用程序编程接口
版本历史
v1.0.0 (当前版本)
- 基础推荐人系统
- 节点购买功能
- 算力管理
- 资金冻结系统
- 子图索引
- API 服务
计划版本
- v1.1.0:多级推荐奖励
- v1.2.0:治理功能
- v2.0.0:多链支持
贡献指南
代码贡献
- Fork 项目仓库
- 创建功能分支
- 提交代码变更
- 创建 Pull Request
文档贡献
- 完善技术文档
- 添加使用示例
- 翻译文档内容
- 更新 API 文档
测试贡献
- 编写测试用例
- 发现和报告 Bug
- 性能测试
- 安全测试
许可证信息
- 智能合约:MIT License
- 子图代码:MIT License
- API 服务:MIT License
- 文档内容:MIT License
文档维护
更新记录
- 2024-01-01:初始版本创建
- [后续更新记录]
维护团队
- 技术负责人:[待补充]
- 文档维护:[待补充]
- 社区支持:[待补充]
反馈渠道
- 技术问题:GitHub Issues
- 文档建议:文档仓库 Issues
- 社区讨论:Discord/Telegram
- 邮件联系:[待补充]
<div style=“text-align: center; margin-top: 50px; color: #666; font-size: 12px;">
<p>本文档由 Dory/Datre 项目团队维护</p>
<p>最后更新时间:2024年</p>
<p>文档版本:v1.0.0</p>
</div>