# DAIX.fun 技术参考手册

> 📋 **版本**：v1.0  
> 📅 **日期**：2026-06-29  
> 🎯 **受众**：Android 开发者、DApp 开发者、合约集成者

---

## 目录

1. [技术架构详解](#1-技术架构详解)
2. [智能合约技术规范](#2-智能合约技术规范)
3. [Web3LicenseManager 集成指南](#3-web3licensemanager-集成指南)
4. [前端 DApp 集成](#4-前端-dapp-集成)
5. [文件上传 API](#5-文件上传-api)
6. [错误处理与调试](#6-错误处理与调试)
7. [测试指南](#7-测试指南)
8. [部署清单](#8-部署清单)

---

## 1. 技术架构详解

### 1.1 系统分层

```
┌─────────────────────────────────────────────────────────┐
│                    展示层 (Presentation)                 │
│  daix.fun 前端 (HTML/CSS/JS) │ Android APP (Java/Kotlin)│
├─────────────────────────────────────────────────────────┤
│                    交互层 (Interaction)                  │
│  ethers.js v6 (Web) │ Web3LicenseManager (Android)      │
├─────────────────────────────────────────────────────────┤
│                    合约层 (Contract)                     │
│  DeveloperRegistry │ AppRegistry │ LicenseNFT           │
│  PromoterRegistry  │ LicenseQuery │ RevenueSplit        │
├─────────────────────────────────────────────────────────┤
│                    结算层 (Settlement)                   │
│  USDC (ERC-20) │ POL (Gas) │ Polygon PoS               │
├─────────────────────────────────────────────────────────┤
│                    存储层 (Storage)                      │
│  IPFS (APK/图标) │ FTP (备用) │ 链上 (元数据)           │
└─────────────────────────────────────────────────────────┘
```

### 1.2 数据流

```
[用户购买流程]
用户钱包 → USDC.approve(LicenseNFT, price)
         → LicenseNFT.mintLicense(appId, agent)
         → 合约内部分账:
              60% → app.developer (开发者钱包)
              25% → agent (推广者钱包, 若 address(0) 则归开发者)
              10% → platformWallet (平台基金)
              5%  → gasWallet (Gas 补贴)
         → _safeMint(user, tokenId)
         → License NFT 铸造到用户钱包

[APP 激活流程]
APP 启动 → Web3LicenseManager.verifyLicense()
         → eth_call(LicenseQuery, verifyLicense(user, appId))
         → 返回 (hasLicense, tokenId)
         → 若 hasLicense=true → 解锁全部功能
         → 若 false → 检查试用期 / 引导购买
```

### 1.3 网络配置

| 参数 | 值 |
|------|-----|
| 网络 | Polygon PoS Mainnet |
| Chain ID | 137 (0x89) |
| RPC | `https://polygon-bor-rpc.publicnode.com` |
| 浏览器 | `https://polygonscan.com` |
| USDC | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` |
| Gas Token | POL (原名 MATIC) |
| Gas Limit | 交易约 100,000-300,000 |
| Gas Price | 约 30-100 Gwei |

---

## 2. 智能合约技术规范

### 2.1 合约地址与版本

| 合约 | 地址 | 版本 | 编译器 |
|------|------|------|--------|
| DeveloperRegistry | `0x8EE775e943C31EA7c4eF2B7bb83e0651c9d04001` | v1.0 | solc 0.8.25 |
| AppRegistry | `0xdFCFA7d871cCc3674d873d00192Ba26a685B2b68` | v1.0 | solc 0.8.25 |
| LicenseNFT | `0x7Ed65226C66b188f66AA0e5483917B1C33a41225` | v1.0 | solc 0.8.25 |
| PromoterRegistry | `0xeed4653f341F810e00A73c6ECD454a1205C0Cabb` | v2.0 | solc 0.8.25 |
| LicenseQuery | `0xb36Fd748026097e0F18A644452E739DECf4d0686` | v2.0 | solc 0.8.25 |
| RevenueSplit | `0x698c00CD9e94353C6ef804e670bc558108250f9D` | v1.0 | solc 0.8.25 |

### 2.2 DeveloperRegistry

```solidity
// 状态变量
mapping(address => Developer) public developers;
address[] public developerList;
uint256 public developerCount;

// 结构体
struct Developer {
    address wallet;       // 钱包地址
    string name;         // 开发者名称
    string metadataURI;  // IPFS 元数据
    uint256 registeredAt; // 注册时间戳
    bool active;         // 是否活跃
}

// 关键函数
function register(string calldata _name, string calldata _metadataURI) external;
function updateProfile(string calldata _name, string calldata _metadataURI) external;
function deactivate() external;
function isRegistered(address _wallet) external view returns (bool);
function getDeveloper(address _wallet) external view returns (string, string, uint256, bool);
function getAllDevelopers() external view returns (address[]);

// 事件
event DeveloperRegistered(address indexed wallet, string name, string metadataURI);
event DeveloperUpdated(address indexed wallet, string name, string metadataURI);
event DeveloperDeactivated(address indexed wallet);

// 修饰符
modifier onlyRegistered()  // 要求调用者已注册
```

**Gas 消耗估算：**
- `register()`: ~120,000 gas
- `updateProfile()`: ~45,000 gas

### 2.3 AppRegistry

```solidity
// 状态变量
uint256 public nextAppId = 1;
mapping(uint256 => App) public apps;
mapping(address => uint256[]) public developerApps;
uint256[] public activeAppIds;
IDeveloperRegistry public devRegistry;  // 依赖 DeveloperRegistry

// 结构体
struct App {
    uint256 appId;
    address developer;
    string name;
    string packageName;     // Android 包名 (唯一标识)
    string description;
    string apkURI;          // APK 下载地址
    string iconURI;         // 图标地址
    uint256 price;          // USDC 价格 (6位小数)
    uint256 trialDays;      // 试用天数
    string version;
    uint256 createdAt;
    uint256 updatedAt;
    bool active;
}

// 关键函数
function registerApp(string, string, string, string, string, uint256, uint256, string) external returns (uint256);
function updateApp(uint256 _appId, string _version, string _apkURI, uint256 _price) external;
function deactivateApp(uint256 _appId) external;
function getApp(uint256 _appId) external view returns (App memory);
function getDeveloperApps(address _developer) external view returns (uint256[]);
function getActiveApps() external view returns (uint256[]);

// 事件
event AppRegistered(uint256 indexed appId, address indexed developer, string name, string packageName, uint256 price);
event AppUpdated(uint256 indexed appId, string version, string apkURI, uint256 price);
event AppDeactivated(uint256 indexed appId);

// 约束
modifier onlyRegisteredDev()  // 要求调用者是已注册开发者
```

**Gas 消耗估算：**
- `registerApp()`: ~200,000 gas
- `updateApp()`: ~50,000 gas
- `deactivateApp()`: ~30,000 gas

**价格编码：**
```javascript
// USDC 6位小数
const price = ethers.parseUnits("198", 6);  // 198 USDC = 198000000
const priceUsdc = ethers.formatUnits(app.price, 6);  // "198.0"
```

### 2.4 LicenseNFT

```solidity
// 继承
contract LicenseNFT is ERC721URIStorage, Ownable

// 状态变量
uint256 private _nextTokenId = 1;
mapping(uint256 => License) public licenses;
mapping(uint256 => uint256) public appLicenseCount;

// 分账常量 (基点, 10000 = 100%)
uint256 public constant DEV_SHARE = 6000;       // 60%
uint256 public constant AGENT_SHARE = 2500;     // 25%
uint256 public constant PLATFORM_SHARE = 1000;  // 10%
uint256 public constant GAS_SHARE = 500;        // 5%

// 关键函数
function mintLicense(uint256 _appId, address _agent) external returns (uint256);
function batchMint(uint256 _appId, uint256 _quantity) external returns (uint256[]);
function activateLicense(uint256 _tokenId, string calldata _deviceFingerprint) external;
function getLicense(uint256 _tokenId) external view returns (License memory);
function totalSupply() external view returns (uint256);

// 事件
event LicenseMinted(uint256 indexed tokenId, uint256 indexed appId, address buyer, uint256 price);
event LicenseActivated(uint256 indexed tokenId, address activator);
event LicenseTransferred(uint256 indexed tokenId, address from, address to);
event RevenueDistributed(uint256 indexed tokenId, uint256 devAmount, uint256 agentAmount, uint256 platformAmount, uint256 gasAmount);
```

**Gas 消耗估算：**
- `mintLicense()`: ~250,000 gas
- `batchMint(10)`: ~1,500,000 gas
- `activateLicense()`: ~60,000 gas

**分账逻辑：**
```solidity
function mintLicense(uint256 _appId, address _agent) external returns (uint256) {
    // 1. 验证 APP
    IAppRegistry.App memory app = appRegistry.getApp(_appId);
    require(app.active, "App not active");
    require(app.price > 0, "App price must be > 0");

    // 2. 转移 USDC
    require(usdc.transferFrom(msg.sender, address(this), app.price), "USDC transfer failed");

    // 3. 计算分账
    uint256 devAmount = (app.price * DEV_SHARE) / 10000;
    uint256 agentAmount = (_agent != address(0)) ? (app.price * AGENT_SHARE) / 10000 : 0;
    uint256 platformAmount = (app.price * PLATFORM_SHARE) / 10000;
    uint256 gasAmount = (app.price * GAS_SHARE) / 10000;
    uint256 devTotal = devAmount + agentAmount;  // 无推广者时归开发者

    // 4. 分账转账
    require(usdc.transfer(app.developer, devTotal), "Dev payment failed");
    require(usdc.transfer(platformWallet, platformAmount), "Platform payment failed");
    require(usdc.transfer(gasWallet, gasAmount), "Gas payment failed");
    if (_agent != address(0)) {
        require(usdc.transfer(_agent, agentAmount), "Agent payment failed");
    }

    // 5. 铸造 NFT
    uint256 tokenId = _nextTokenId++;
    _safeMint(msg.sender, tokenId);
    _setTokenURI(tokenId, app.iconURI);

    // 6. 记录
    licenses[tokenId] = License({
        appId: _appId,
        developer: app.developer,
        mintPrice: app.price,
        mintedAt: block.timestamp,
        activated: false,
        holder: msg.sender
    });

    return tokenId;
}
```

### 2.5 PromoterRegistry

```solidity
// 关键函数
function register(string calldata name, string calldata referralCode) external;
function updateName(string calldata newName) external;
function isPromoter(address addr) external view returns (bool);
function getByCode(string calldata code) external view returns (address);
function getPromoter(address addr) external view returns (string, string, uint256, bool);

// 约束
// - referralCode 全局唯一
// - 一个地址只能注册一次
// - 只有 owner 可以 deactivate
```

### 2.6 LicenseQuery

```solidity
// 只读查询合约 (无需 Gas)

function verifyLicense(address user, uint256 appId) external view returns (bool hasLicense, uint256 tokenId);
function getUserLicenses(address user) external view returns (uint256[] memory tokenIds, uint256[] memory appIds);
function getAppByPackageName(string calldata packageName) external view returns (uint256 appId, bool found);
function getAppsByDeveloper(address dev) external view returns (uint256[] memory appIds);
```

**注意：** `verifyLicense` 遍历用户所有 NFT，当持有量大时 Gas 消耗较高。建议链下查询。

### 2.7 完整 ABI (ethers.js v6 格式)

```javascript
const ABIS = {
  DeveloperRegistry: [
    "function register(string name, string metadataURI)",
    "function updateProfile(string name, string metadataURI)",
    "function isRegistered(address) view returns (bool)",
    "function getDeveloper(address) view returns (string, string, uint256, bool)",
    "function developerCount() view returns (uint256)",
    "function getAllDevelopers() view returns (address[])",
  ],
  AppRegistry: [
    "function registerApp(string name, string packageName, string description, string apkURI, string iconURI, uint256 price, uint256 trialDays, string version) returns (uint256)",
    "function updateApp(uint256 appId, string version, string apkURI, uint256 price)",
    "function deactivateApp(uint256 appId)",
    "function getApp(uint256) view returns (tuple(uint256,address,string,string,string,string,string,uint256,uint256,string,uint256,uint256,bool))",
    "function getDeveloperApps(address) view returns (uint256[])",
    "function getActiveApps() view returns (uint256[])",
    "function nextAppId() view returns (uint256)",
    "function apps(uint256) view returns (uint256,address,string,string,string,string,string,uint256,uint256,string,uint256,uint256,bool)",
  ],
  LicenseNFT: [
    "function mintLicense(uint256 appId, address agent) returns (uint256)",
    "function batchMint(uint256 appId, uint256 quantity) returns (uint256[])",
    "function activateLicense(uint256 tokenId, string deviceFingerprint)",
    "function getLicense(uint256) view returns (tuple(uint256,address,uint256,uint256,bool,address))",
    "function totalSupply() view returns (uint256)",
    "function ownerOf(uint256) view returns (address)",
    "function balanceOf(address) view returns (uint256)",
    "function tokenOfOwnerByIndex(address, uint256) view returns (uint256)",
    "function licenses(uint256) view returns (uint256,address,uint256,uint256,bool,address)",
    "function transferFrom(address, address, uint256)",
    "function safeTransferFrom(address, address, uint256)",
    "function DEV_SHARE() view returns (uint256)",
    "function AGENT_SHARE() view returns (uint256)",
    "function PLATFORM_SHARE() view returns (uint256)",
    "function GAS_SHARE() view returns (uint256)",
  ],
  LicenseQuery: [
    "function verifyLicense(address, uint256) view returns (bool, uint256)",
    "function getUserLicenses(address) view returns (uint256[], uint256[])",
    "function getAppByPackageName(string) view returns (uint256, bool)",
    "function getAppsByDeveloper(address) view returns (uint256[])",
  ],
  PromoterRegistry: [
    "function register(string name, string referralCode)",
    "function updateName(string newName)",
    "function isPromoter(address) view returns (bool)",
    "function getByCode(string) view returns (address)",
    "function getPromoter(address) view returns (string, string, uint256, bool)",
  ],
  USDC: [
    "function approve(address spender, uint256 amount) returns (bool)",
    "function allowance(address owner, address spender) view returns (uint256)",
    "function balanceOf(address) view returns (uint256)",
    "function decimals() view returns (uint256)",
    "function transfer(address to, uint256 amount) returns (bool)",
  ],
};
```

---

## 3. Web3LicenseManager 集成指南

### 3.1 文件位置

```
app/src/main/java/你的包名/data/Web3LicenseManager.java
```

### 3.2 依赖库

```gradle
// build.gradle.kts
dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    implementation("org.bouncycastle:bcprov-jdk18on:1.77")
    implementation("org.json:json:20231013")
}
```

### 3.3 类结构

```java
public class Web3LicenseManager {
    // 常量
    private static final String RPC_URL = "https://polygon-bor-rpc.publicnode.com";
    private static final String LICENSE_QUERY = "0xb36Fd748026097e0F18A644452E739DECf4d0686";
    private static final String LICENSE_NFT = "0x7Ed65226C66b188f66AA0e5483917B1C33a41225";
    private static final long CHAIN_ID = 137;
    private static final long TRIAL_DAYS = 7;

    // 构造函数
    public Web3LicenseManager(Context ctx, String packageName);

    // 钱包管理
    public boolean hasWallet();
    public String getAddress();
    public String getPrivateKey();
    public boolean importWallet(String privateKeyHex);
    public void clearWallet();

    // NFT 验证
    public void verifyLicense(VerifyCallback callback);
    public boolean isVerified();
    public long getCachedAppId();

    // 试用期
    public long getTrialStart();
    public boolean isTrialExpired();
    public long getTrialRemaining();

    // 激活状态
    public boolean isActivated();
    public String getStatusText();
    public boolean activateOffline(String code);

    // 回调接口
    public interface VerifyCallback {
        void onResult(boolean verified, String message, long appId);
    }
}
```

### 3.4 集成步骤

#### Step 1: 初始化

```java
// 在 Activity.onCreate() 中
Web3LicenseManager licenseMgr = new Web3LicenseManager(this, getPackageName());
```

#### Step 2: 检查激活状态

```java
if (licenseMgr.isActivated()) {
    // 已激活 → 进入主界面
    showMainContent();
} else if (!licenseMgr.isTrialExpired()) {
    // 试用期内 → 显示试用提示
    long remaining = licenseMgr.getTrialRemaining();
    long days = remaining / 86400000;
    showTrialBanner(days + "天");
} else {
    // 试用过期 → 显示激活对话框
    showActivationDialog();
}
```

#### Step 3: 验证 NFT

```java
licenseMgr.verifyLicense((verified, message, appId) -> {
    runOnUiThread(() -> {
        if (verified) {
            Toast.makeText(this, "✅ NFT 验证通过", Toast.LENGTH_SHORT).show();
            showMainContent();
        } else {
            // 未持有 NFT
            new AlertDialog.Builder(this)
                .setTitle("需要激活")
                .setMessage("你尚未持有此 APP 的数字凭证。\n\n请访问 https://daix.fun 购买。")
                .setPositiveButton("去购买", (d, w) -> {
                    startActivity(new Intent(Intent.ACTION_VIEW,
                        Uri.parse("https://daix.fun")));
                })
                .setNegativeButton("输入激活码", (d, w) -> {
                    showOfflineActivationDialog();
                })
                .show();
        }
    });
});
```

#### Step 4: 导入钱包

```java
// 从用户输入获取私钥
String privateKey = editText.getText().toString().trim();
boolean success = licenseMgr.importWallet(privateKey);
if (success) {
    Toast.makeText(this, "钱包导入成功", Toast.LENGTH_SHORT).show();
    licenseMgr.verifyLicense(callback);  // 立即验证
} else {
    Toast.makeText(this, "私钥格式无效", Toast.LENGTH_SHORT).show();
}
```

#### Step 5: 离线激活

```java
String code = editText.getText().toString().trim();
boolean ok = licenseMgr.activateOffline(code);
if (ok) {
    Toast.makeText(this, "✅ 离线激活成功", Toast.LENGTH_SHORT).show();
    showMainContent();
} else {
    Toast.makeText(this, "激活码无效", Toast.LENGTH_SHORT).show();
}
```

### 3.5 状态机

```
┌─────────┐     ┌──────────┐     ┌──────────┐
│  初始    │────▶│  试用中   │────▶│ 试用过期  │
│  状态    │     │ (7天)    │     │          │
└─────────┘     └──────────┘     └──────────┘
     │               │                │
     │               │                │
     ▼               ▼                ▼
┌─────────────────────────────────────────┐
│           已激活 (isActivated)           │
│  ├── NFT 验证通过 (isVerified)          │
│  └── 离线激活码 (offline_activated)     │
└─────────────────────────────────────────┘
```

### 3.6 SharedPreferences 存储

| Key | 类型 | 说明 |
|-----|------|------|
| `private_key` | String | 钱包私钥 (hex) |
| `wallet_address` | String | 钱包地址 |
| `nft_verified` | boolean | NFT 是否已验证 |
| `chain_app_id` | long | 链上 APP ID |
| `trial_start` | long | 试用开始时间戳 |
| `offline_activated` | boolean | 离线激活码是否已激活 |

---

## 4. 前端 DApp 集成

### 4.1 钱包连接

```javascript
async function connectWallet() {
  // 1. 检查 MetaMask
  if (!window.ethereum) {
    alert("请安装 MetaMask");
    return;
  }

  // 2. 请求连接
  const accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
  const account = accounts[0];

  // 3. 切换到 Polygon
  const chainId = await window.ethereum.request({ method: "eth_chainId" });
  if (chainId !== "0x89") {
    try {
      await window.ethereum.request({
        method: "wallet_switchEthereumChain",
        params: [{ chainId: "0x89" }],
      });
    } catch (e) {
      // 添加网络
      await window.ethereum.request({
        method: "wallet_addEthereumChain",
        params: [{
          chainId: "0x89",
          chainName: "Polygon Mainnet",
          rpcUrls: ["https://polygon-bor-rpc.publicnode.com"],
          blockExplorerUrls: ["https://polygonscan.com"],
          nativeCurrency: { name: "POL", symbol: "POL", decimals: 18 },
        }],
      });
    }
  }

  // 4. 初始化 ethers
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();

  return { provider, signer, account };
}
```

### 4.2 购买流程

```javascript
async function buyLicense(appId, agentAddress) {
  const { signer, account } = await connectWallet();

  // 合约实例
  const usdc = new ethers.Contract(USDC_ADDR, USDC_ABI, signer);
  const nft = new ethers.Contract(LICENSE_NFT, NFT_ABI, signer);

  // 获取价格
  const app = await nft.appRegistry().then(addr => {
    const reg = new ethers.Contract(addr, APP_ABI, signer);
    return reg.getApp(appId);
  });
  const price = app.price;

  // Step 1: 检查/授权 USDC
  const allowance = await usdc.allowance(account, LICENSE_NFT);
  if (allowance < price) {
    const tx = await usdc.approve(LICENSE_NFT, price);
    await tx.wait();  // 等待授权确认
  }

  // Step 2: 购买
  const agent = agentAddress || "0x0000000000000000000000000000000000000000";
  const tx = await nft.mintLicense(appId, agent);
  const receipt = await tx.wait();  // 等待购买确认

  // Step 3: 提取 tokenId
  const event = receipt.logs.find(log => {
    try { return nft.interface.parseLog(log)?.name === "LicenseMinted"; }
    catch { return false; }
  });
  const tokenId = nft.interface.parseLog(event).args.tokenId;

  return { tokenId, txHash: receipt.hash };
}
```

### 4.3 查询我的许可证

```javascript
async function getMyLicenses(account) {
  const provider = new ethers.JsonRpcProvider(RPC_URL);
  const nft = new ethers.Contract(LICENSE_NFT, NFT_ABI, provider);
  const appReg = new ethers.Contract(APP_REGISTRY, APP_ABI, provider);

  const balance = await nft.balanceOf(account);
  const licenses = [];

  for (let i = 0; i < Number(balance); i++) {
    const tokenId = await nft.tokenOfOwnerByIndex(account, i);
    const lic = await nft.licenses(tokenId);
    const app = await appReg.getApp(lic.appId);

    licenses.push({
      tokenId: Number(tokenId),
      appId: Number(lic.appId),
      appName: app.name,
      packageName: app.packageName,
      price: ethers.formatUnits(lic.mintPrice, 6),
      purchasedAt: new Date(Number(lic.mintedAt) * 1000),
      activated: lic.activated,
      code: "NFT-" + Number(tokenId).toString(16).toUpperCase().padStart(6, "0"),
    });
  }

  return licenses;
}
```

### 4.4 推广链接处理

```javascript
// 页面加载时解析推广参数
const urlParams = new URLSearchParams(window.location.search);
const agentParam = urlParams.get("agent");
let referrerAddress = null;

if (agentParam && /^0x[a-fA-F0-9]{40}$/.test(agentParam)) {
  referrerAddress = agentParam;
  // 存储到 localStorage 以便后续使用
  localStorage.setItem("daix-referrer", referrerAddress);
} else {
  referrerAddress = localStorage.getItem("daix-referrer");
}

// 购买时传入推广者地址
await buyLicense(appId, referrerAddress);
```

---

## 5. 文件上传 API

### 5.1 FTP 上传

| 参数 | 值 |
|------|-----|
| 服务器 | `43.166.240.93:21` |
| 用户名 | `hackathon` |
| 密码 | `hackathon2026` |
| 模式 | **主动模式** (Active) |

**目录结构：**
```
/
├── apk/       ← APK 文件
├── icons/     ← 应用图标
└── docs/      ← 文档
```

**Python 上传示例：**
```python
from ftplib import FTP

ftp = FTP()
ftp.connect("43.166.240.93", 21, timeout=15)
ftp.login("hackathon", "hackathon2026")
ftp.set_pasv(False)  # 必须主动模式

with open("app-release.apk", "rb") as f:
    ftp.storbinary("STOR /apk/com.example.app-v1.0.0.apk", f)

ftp.quit()
```

**curl 上传：**
```bash
curl -T app-release.apk ftp://43.166.240.93/apk/ \
  --user hackathon:hackathon2026 \
  --ftp-pasv
```

### 5.2 Python 上传工具

```bash
# 下载工具
curl -O https://daix.fun/daix-upload.py

# 上传 APK
python3 daix-upload.py apk ./app-release.apk --pkg com.example.app --ver v1.0.0

# 上传图标
python3 daix-upload.py icon ./icon.png --pkg com.example.app

# 上传文档
python3 daix-upload.py doc ./README.md

# 查看目录
python3 daix-upload.py list /apk
```

### 5.3 文件命名规范

| 类型 | 格式 | 示例 |
|------|------|------|
| APK | `{packageName}-{version}.apk` | `com.storeguard.vipreception-v1.0.0.apk` |
| 图标 | `{packageName}.png` | `com.storeguard.vipreception.png` |
| 文档 | 任意 | `README.md` |

### 5.4 HTTP 上传 (备用)

```bash
# ClawClaw 服务器
curl -X POST http://43.166.240.93:3030/api/upload-public \
  -F "file=@app-release.apk"
```

---

## 6. 错误处理与调试

### 6.1 常见合约错误

| 错误信息 | 原因 | 解决方案 |
|----------|------|----------|
| `Already registered` | 重复注册开发者 | 检查 `isRegistered()` |
| `Not a registered developer` | 未注册就发布 APP | 先调用 `register()` |
| `App not active` | APP 已下架 | 检查 `app.active` |
| `USDC transfer failed` | USDC 余额不足或未授权 | 检查余额 + `approve()` |
| `Not the owner` | 非 APP 所有者操作 | 确认调用者地址 |
| `Referral code already taken` | 推广码已被占用 | 更换推广码 |
| `Already activated` | NFT 已激活过 | 检查 `licenses[tokenId].activated` |

### 6.2 前端错误处理

```javascript
async function safeContractCall(fn, ...args) {
  try {
    const tx = await fn(...args);
    const receipt = await tx.wait();
    return { success: true, receipt };
  } catch (err) {
    // 解析错误
    let message = "交易失败";

    if (err.code === "ACTION_REJECTED") {
      message = "用户取消了交易";
    } else if (err.code === "INSUFFICIENT_FUNDS") {
      message = "余额不足";
    } else if (err.message.includes("execution reverted")) {
      // 提取 revert 原因
      const match = err.message.match(/reason="([^"]+)"/);
      message = match ? match[1] : "合约执行失败";
    }

    return { success: false, error: message };
  }
}
```

### 6.3 RPC 调试

```javascript
// 直接调用 RPC
async function ethCall(to, data) {
  const response = await fetch(RPC_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "eth_call",
      params: [{ to, data }, "latest"],
    }),
  });
  const result = await response.json();
  return result.result;
}

// 示例：查询 APP 信息
const data = "0x8da5cb5b" + ethers.zeroPadValue(ethers.toBeHex(1), 32).slice(2);
const result = await ethCall(APP_REGISTRY, data);
```

### 6.4 事件监听

```javascript
// 监听购买事件
const nft = new ethers.Contract(LICENSE_NFT, NFT_ABI, provider);

nft.on("LicenseMinted", (tokenId, appId, buyer, price) => {
  console.log(`新购买: Token #${tokenId}, APP ${appId}, 买家 ${buyer}`);
});

nft.on("RevenueDistributed", (tokenId, devAmt, agentAmt, platAmt, gasAmt) => {
  console.log(`分账: 开发者 ${devAmt}, 推广者 ${agentAmt}`);
});
```

---

## 7. 测试指南

### 7.1 本地测试 (Hardhat)

```bash
cd contracts
npm install

# 编译合约
npx hardhat compile

# 运行测试
npx hardhat test

# 本地部署
npx hardhat node
npx hardhat run scripts/deploy.ts --network localhost
```

### 7.2 测试网测试 (Mumbai)

```bash
# 配置 .env
MUMBAI_RPC_URL=https://rpc-mumbai.maticvigil.com
PRIVATE_KEY=your_private_key

# 部署到测试网
npx hardhat run scripts/deploy.ts --network mumbai
```

### 7.3 端到端测试脚本

```javascript
// test-e2e.js
const { ethers } = require("ethers");

async function testE2E() {
  const provider = new ethers.JsonRpcProvider(RPC_URL);

  // 1. 查询合约状态
  const devReg = new ethers.Contract(DEV_REG, DEV_ABI, provider);
  const count = await devReg.developerCount();
  console.log("开发者数量:", count);

  // 2. 查询 APP
  const appReg = new ethers.Contract(APP_REG, APP_ABI, provider);
  const nextId = await appReg.nextAppId();
  console.log("APP 数量:", Number(nextId) - 1);

  // 3. 查询许可证
  const query = new ethers.Contract(QUERY, QUERY_ABI, provider);
  const [appId, found] = await query.getAppByPackageName("com.localguard.pedestriancapture");
  console.log("单点抓拍:", found ? `appId=${appId}` : "未找到");

  // 4. 验证分账比例
  const nft = new ethers.Contract(NFT, NFT_ABI, provider);
  const dev = await nft.DEV_SHARE();
  const agent = await nft.AGENT_SHARE();
  console.log("分账:", Number(dev)/100 + Number(agent)/100 + "%");
}

testE2E().catch(console.error);
```

### 7.4 测试检查清单

- [ ] DeveloperRegistry 注册/查询
- [ ] AppRegistry 发布/更新/下架
- [ ] LicenseNFT 购买/分账
- [ ] LicenseQuery 验证/查询
- [ ] PromoterRegistry 注册/查询
- [ ] USDC 授权/转账
- [ ] 推广链接参数传递
- [ ] Web3LicenseManager NFT 验证
- [ ] 试用期逻辑
- [ ] 离线激活码

---

## 8. 部署清单

### 8.1 上架前检查

- [ ] APK 已签名 (Release keystore)
- [ ] APK 已上传到 FTP/IPFS
- [ ] 图标已上传
- [ ] 包名唯一 (链上检查)
- [ ] 价格已设定 (USDC, 6位小数)
- [ ] 试用天数已设定 (≥1)
- [ ] 开发者已注册
- [ ] 测试网验证通过

### 8.2 上架步骤

```bash
# 1. 上传 APK
python3 daix-upload.py apk ./app-release.apk \
  --pkg com.example.app --ver v1.0.0

# 2. 上传图标
python3 daix-upload.py icon ./icon.png --pkg com.example.app

# 3. 链上注册
# 通过 daix.fun 前端或合约调用
```

### 8.3 更新版本

```bash
# 1. 上传新 APK
python3 daix-upload.py apk ./app-v1.1.0.apk \
  --pkg com.example.app --ver v1.1.0

# 2. 链上更新
# 调用 AppRegistry.updateApp(appId, "v1.1.0", newApkUri, newPrice)
```

---

## 附录：快速参考

### 合约地址

```
DeveloperRegistry : 0x8EE775e943C31EA7c4eF2B7bb83e0651c9d04001
AppRegistry       : 0xdFCFA7d871cCc3674d873d00192Ba26a685B2b68
LicenseNFT        : 0x7Ed65226C66b188f66AA0e5483917B1C33a41225
PromoterRegistry  : 0xeed4653f341F810e00A73c6ECD454a1205C0Cabb
LicenseQuery      : 0xb36Fd748026097e0F18A644452E739DECf4d0686
USDC              : 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359
```

### 函数选择器

```
DeveloperRegistry.register(string,string)     0x91b7f579
DeveloperRegistry.isRegistered(address)        0xa1665669
AppRegistry.registerApp(...)                   0xf2c89d1d
AppRegistry.getApp(uint256)                    0x8da5cb5b
AppRegistry.getActiveApps()                    0x22064bfc
AppRegistry.updateApp(...)                     0x1f92e2dc
AppRegistry.deactivateApp(uint256)             0x2dbe0ade
LicenseNFT.mintLicense(uint256,address)        0x4d28d8aa
LicenseNFT.batchMint(uint256,uint256)          0x5e597d90
LicenseNFT.activateLicense(uint256,string)     0x2e1a7d4d
LicenseQuery.verifyLicense(address,uint256)    0xae0226b3
LicenseQuery.getUserLicenses(address)          0xc7a3bc00
LicenseQuery.getAppByPackageName(string)       0x58e30592
PromoterRegistry.register(string,string)       0xf2c89d1d
```

### 附录 D：全部文档链接

| 资源 | URL | 说明 |
|------|-----|------|
| 前端 | https://daix.fun | DAIX.fun 主站 |
| Agent Playbook | https://daix.fun/agent-playbook.md | 🤖 AI 自动执行手册 |
| 技术参考手册 | https://daix.fun/technical-reference.md | 本文档 |
| 开发者接入标准 | https://daix.fun/developer-guide.md | 业务流程与定价 |
| 推广者指南 | https://daix.fun/promoter-guide.md | 推广码与佣金 |
| 用户指南 | https://daix.fun/user-guide.md | 购买与激活教程 |
| 控制面板文档 | https://daix.fun/control-panel.md | 管理后台技术文档 |
| FTP 上传工具 | https://daix.fun/daix-upload.py | Python 上传脚本 |
| 自动化脚本 | https://daix.fun/daix-automation.sh | Shell 自动化工具集 |
| GitHub 源码 | https://github.com/zhulianxing/daix-fun | 合约+前端+APP |
| Polygonscan | https://polygonscan.com | 链上浏览器 |

---

*最后更新：2026-06-29 by CONVNET Team*
