推荐系统 API (Referral)
后端版本: v0.3.0
概述
AXBlade Referral 系统支持推荐码注册、绑定、返佣查询和链上领取功能。
合约地址 (Arbitrum Sepolia Testnet)
| 合约 | 地址 | 说明 |
|---|---|---|
| USDT | 0x572E474C3Cf364D085760784F938A1Aa397a8B9b | 测试 USDT (6 decimals) |
| ZTDXVault | 0xFDe43f8e6e082975d246844DEF4fE8E704403d43 | 充值/提现合约 |
| ReferralStorage | 0x984BC1C4Aa3995F9B78BA54372a10d3088C249D5 | 推荐码存储合约 |
| ReferralRebate | 0xaF486e11c824389E4Ab3ced7608ac3Bd43c176B8 | 返佣分发合约 |
Chain ID: 421614
RPC URL: https://sepolia-rollup.arbitrum.io/rpc
1. 链上查询接口 (无需认证)
1.1 获取用户返佣信息
GET /referral/on-chain/user-rebate/:address
响应
{
"address": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"claimed_usd": "0",
"nonce": 0,
"referral_code": "",
"referrer": "0x0000000000000000000000000000000000000000",
"tier_level": 0,
"tier_name": "Bronze"
}
1.2 获取推荐关系信息
GET /referral/on-chain/referral-info/:address
响应
{
"address": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"code": "ALICE2024",
"referrer": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
"total_rebate_bps": 1000,
"trader_discount_bps": 500,
"affiliate_reward_bps": 500
}
1.3 获取已领取金额
GET /referral/on-chain/claimed/:address
响应
{
"address": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"claimed_usd": "150.50"
}
2. 推荐码管理 (需要认证)
2.1 创建推荐码
POST /referral/codes
Content-Type: application/json
Authorization: Bearer <jwt_token>
{
"timestamp": 1703577600,
"signature": "0x..."
}
EIP-712 签名结构
const types = {
CreateReferralCode: [
{ name: "wallet", type: "address" },
{ name: "timestamp", type: "uint256" }
]
};
响应
{
"success": true,
"code": "A1B2C3",
"created_at": 1703577600000
}
2.2 绑定推荐码
POST /referral/bind
Content-Type: application/json
Authorization: Bearer <jwt_token>
{
"code": "ALICE2024",
"timestamp": 1703577600,
"signature": "0x..."
}
EIP-712 签名结构
const types = {
BindReferralCode: [
{ name: "wallet", type: "address" },
{ name: "code", type: "string" },
{ name: "timestamp", type: "uint256" }
]
};
2.3 获取推荐仪表板
GET /referral/dashboard
Authorization: Bearer <jwt_token>
响应
{
"code": "A1B2C3",
"total_referrals": 25,
"active_referrals": 18,
"total_earnings": "1250.50",
"pending_earnings": "150.00",
"claimed_earnings": "1100.50",
"tier": {
"level": 2,
"name": "Gold",
"commission_rate": "0.15",
"next_tier_requirement": 50
},
"recent_activity": [
{
"referral_address": "0x1234...",
"event_type": "trade",
"volume": "10000.00",
"commission": "15.00",
"timestamp": 1703577600000
}
]
}
3. 链上领取返佣
3.1 获取领取签名
POST /referral/on-chain/claim-signature
Content-Type: application/json
Authorization: Bearer <jwt_token>
{
"amount": "100.50"
}
响应
{
"amount": "100500000",
"nonce": 0,
"deadline": 1766726997,
"signature": "0x38f1fcf0e7a31bee39a0e941a70f7fec6b6352fc...",
"contract_address": "0xaF486e11c824389E4Ab3ced7608ac3Bd43c176B8"
}
3.2 调用合约领取
import { ethers } from 'ethers';
const REBATE_CONTRACT = "0xaF486e11c824389E4Ab3ced7608ac3Bd43c176B8";
const REBATE_ABI = [
"function claimRebate(uint256 amount, uint256 deadline, bytes signature) external"
];
async function claimRebate(
signer: ethers.Signer,
amount: bigint,
deadline: number,
signature: string
) {
const contract = new ethers.Contract(REBATE_CONTRACT, REBATE_ABI, signer);
const tx = await contract.claimRebate(amount, deadline, signature);
return await tx.wait();
}
3.3 完整 claimRebate 流程
# Step 1: 获取 Nonce 并登录
curl -s "http://localhost:8080/api/v1/auth/nonce/0x85bfdfee..."
# Step 2: 获取 Claim Signature
curl -X POST "http://localhost:8080/api/v1/referral/on-chain/claim-signature" \
-H "Authorization: Bearer eyJ..." \
-d '{"amount": "10.0"}'
# Step 3: 调用链上 claimRebate
cast send --rpc-url "https://sepolia-rollup.arbitrum.io/rpc" \
--private-key "0x..." \
"0xaF486e11c824389E4Ab3ced7608ac3Bd43c176B8" \
"claimRebate(uint256,uint256,bytes)" \
"10000000" "1766729875" "0x0eec1999..."
常见错误及解决方案
| 错误 | 原因 | 解决方 案 |
|---|---|---|
InvalidSignature() | backendSigner 不匹配 | 更新合约 backendSigner |
SafeERC20: low-level call failed | 合约 USDT 余额不足 | 向合约转入 USDT |
insufficient funds | 用户 ETH 不足 | 确保用户有 ETH 支付 Gas |
DeadlineExceeded() | 签名已过期 | 重新获取 claim signature |
InvalidNonce() | Nonce 不匹配 | 检查链上 nonce |
4. 合约直接交互
4.1 注册推荐码 (链上)
const STORAGE_CONTRACT = "0x984BC1C4Aa3995F9B78BA54372a10d3088C249D5";
const STORAGE_ABI = [
"function registerCode(bytes32 code) external",
"function codeOwners(bytes32 code) view returns (address)"
];
async function registerReferralCode(signer: ethers.Signer, codeString: string) {
const contract = new ethers.Contract(STORAGE_CONTRACT, STORAGE_ABI, signer);
const codeBytes32 = ethers.encodeBytes32String(codeString);
const tx = await contract.registerCode(codeBytes32);
await tx.wait();
}
4.2 充值时绑定推荐码
async function depositWithReferral(
signer: ethers.Signer,
amountUsdt: string,
referralCode: string
) {
const vault = new ethers.Contract(VAULT, VAULT_ABI, signer);
const amount = ethers.parseUnits(amountUsdt, 6);
const codeBytes32 = referralCode
? ethers.encodeBytes32String(referralCode)
: ethers.ZeroHash;
const tx = await vault.deposit(amount, codeBytes32);
await tx.wait();
}
5. 层级系统
| 层级 | 名称 | 总返佣 | 交易者折扣 | 推广者奖励 |
|---|---|---|---|---|
| 0 | Bronze | 10% | 5% | 5% |
| 1 | Silver | 12% | 5% | 7% |
| 2 | Gold | 15% | 5% | 10% |
| 3 | Platinum | 18% | 6% | 12% |
| 4 | Diamond | 20% | 6% | 14% |
6. 错误码
| 错误码 | 说明 |
|---|---|
| INVALID_ADDRESS | 无效的钱包地址格式 |
| CHAIN_ERROR | 链上调用失败 |
| CODE_EXISTS | 推荐码已存在 |
| ALREADY_BOUND | 用户已绑定推荐码 |
| INVALID_CODE | 无效的推荐码 |
| SIGNATURE_INVALID | 签名验证失败 |
| TIMESTAMP_EXPIRED | 时间戳过期 (5分钟内有效) |
7. 完整示例
React Hook 示例
import { useState, useEffect } from 'react';
import { useAccount } from 'wagmi';
const API_BASE = 'https://api.axblade.io/api/v1';
export function useReferral() {
const { address } = useAccount();
const [rebateInfo, setRebateInfo] = useState(null);
const [loading, setLoading] = useState(false);
const fetchRebateInfo = async () => {
if (!address) return;
setLoading(true);
try {
const res = await fetch(
`${API_BASE}/referral/on-chain/user-rebate/${address}`
);
const data = await res.json();
setRebateInfo(data);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchRebateInfo();
}, [address]);
return { rebateInfo, loading, refetch: fetchRebateInfo };
}
8. 事件监听
ReferralRebate 事件
event RebateClaimed(address indexed user, uint256 amount, uint256 nonce);
event RebateBatchSynced(uint256 indexed batchId, uint256 totalAmount, uint256 userCount);
监听示例
const contract = new ethers.Contract(REBATE_CONTRACT, REBATE_ABI, provider);
contract.on("RebateClaimed", (user, amount, nonce, event) => {
console.log(`User ${user} claimed ${ethers.formatUnits(amount, 6)} USDT`);
});
9. 安全注意事项
- 签名有效期: 所有 EIP-712 签名的 timestamp 必须在 5 分钟内
- Nonce 管理: 每次领取返佣后 nonce 自动递增,防止重放攻击
- 金额验证: 后端会验证领取金额与待领取金额是否匹配
- 推荐码唯一性: 每个推荐码只能被一个地址注册
- 单次绑定: 用户只能绑定一次推荐码,绑定后不可更改