> For the complete documentation index, see [llms.txt](https://support.safeheron.com/help-center/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://support.safeheron.com/help-center/jian-ti-zhong-wen/chan-pin/shen-ru-safeheron/safeheron-skill.md).

# Safeheron Skill

### 1. 产品说明

#### 1.1 什么是 Safeheron Skill

Safeheron Skill 是一个面向 **Claude Code** 和 **Cursor** **IDE** 的 AI Skill插件。安装后，开发者可以通过**自然语言描述需求**，让 AI 直接生成基于 Safeheron Java SDK 的生产级代码，无需逐页翻阅 API 文档。

它的核心能力包括：

* **代码生成** —— 描述您的业务场景，AI 自动生成符合 SDK 规范的 Java 代码，包含正确的 import、异常处理和安全校验。
* **全 API 覆盖** —— 涵盖钱包管理、转账交易、MPC 签名、Web3 签名、Webhook 回调、白名单、Gas Station、AML/KYT 合规检查、Co-Signer 审批等全部 API。
* **调试排错** —— 输入错误码或异常信息，AI 直接定位根因并给出修复方案。
* **安全合规内置** —— 生成的每一段代码都遵循 Safeheron 安全最佳实践，包括密钥管理、幂等性、AML 检查等。

#### 1.2 适用人群

| 角色    | 使用场景                           |
| ----- | ------------------------------ |
| 后端开发者 | 快速生成 SDK 集成代码，减少查阅文档时间         |
| 技术架构师 | 了解 Safeheron API 能力边界，设计系统架构方案 |
| 安全工程师 | 审查代码安全性，了解 MPC 自托管的安全要求        |
| 产品经理  | 快速了解 Safeheron 支持的业务场景和能力范围    |

#### 1.3 核心概念速览

在进入实战之前，您需要了解几个关键概念：

**MPC 自托管（MPC Self-Custody）**：Safeheron 采用 3-of-3 MPC（安全多方计算）架构管理私钥。私钥永远不会以完整形式出现在任何单一设备上，三个分片分别由团队本地、Safeheron 云端持有，协同签名完成交易。

**钱包账户（Wallet Account）**：Safeheron 中的钱包账户是一个**全链钱包**——每个账户可以持有多条链的地址（如 ETH、BTC、USDT 等）。推荐的做法是**一个终端用户绑定一个 Safeheron 钱包账户（1:1 关系）**。

**SDK 调用模式**：Safeheron Java SDK 使用固定的三步模式：

```plaintext
SafeheronConfig → ServiceCreator.create() → ServiceExecutor.execute()
```

所有 API 调用**必须**通过 `ServiceExecutor.execute()` 包装，直接调用接口方法会失败。

**交易生命周期**：

```plaintext
SUBMITTED → WAIT_AUDIT → WAIT_SIGN → BROADCASTING → PENDING → SUCCESS
                ↓              ↓
            REJECTED       FAILED / CANCELLED
```

#### 1.4 Safeheron Skill 内置知识库

Skill 内部包含 20 份专题参考文档，覆盖 Safeheron API 的方方面面：

| 分类        | 文档                           | 说明                             |
| --------- | ---------------------------- | ------------------------------ |
| 入门        | GETTING\_STARTED.md          | 从零到第一个 API 调用                  |
| SDK       | SDK\_SETUP.md                | Maven/Gradle 配置、Spring Boot 集成 |
| 认证        | AUTH.md                      | RSA+AES 加密签名流程                 |
| 钱包        | WALLET\_API.md               | 钱包创建、查询、币种管理                   |
| 交易        | TRANSACTION\_API.md          | 转账、查询、取消、加速                    |
| MPC 签名    | MPC\_SIGN\_API.md            | 原始哈希签名                         |
| Web3      | WEB3\_API.md                 | EVM 链签名操作                      |
| Webhook   | WEBHOOK.md                   | 事件回调处理                         |
| 白名单       | WHITELIST\_API.md            | 地址白名单管理                        |
| 合规        | COMPLIANCE\_API.md           | AML/KYT 报告                     |
| 工具        | TOOLS\_API.md                | AML 地址风控检查                     |
| Gas       | GAS\_API.md                  | Gas Station 状态查询               |
| Co-Signer | COSIGNER.md                  | 自动审批回调                         |
| 安全        | SECURITY\_BEST\_PRACTICES.md | 安全编码规范                         |
| 检查清单      | SECURITY\_CHECKLIST.md       | 上线前安全检查                        |
| 策略        | POLICY\_STRATEGY.md          | 审批策略配置                         |
| 业务模式      | BUSINESS\_PATTERNS.md        | 充值/提币/归集架构                     |
| 错误码       | ERROR\_CODES.md              | 常见错误排查                         |
| FAQ       | FAQ.md                       | 真实场景 Q\&A                      |
| 币种        | COIN\_API.md                 | 币种查询、地址校验                      |

***

### 2. 环境准备与安装

#### 2.1 前置条件

* Java 8+
* Maven 3.x 或 Gradle 7+
* OpenSSL（macOS/Linux 自带，Windows 可使用 Git Bash 或 WSL）
* Safeheron Web Console 账户（<https://www.safeheron.com>）
* Claude Code 或 Cursor IDE

#### 2.2 安装 Skill

**Claude Code 安装**

**方式一：插件安装（推荐）**

```bash
claude plugin add safeheron/safeheron-skill
```

**方式二：手动安装（项目级）**

```bash
git clone https://github.com/absorprofess/safeheron-skill.git
mkdir -p .claude/skills
cp -r safeheron-skill/skills/safeheron .claude/skills/safeheron
```

**方式三：手动安装（用户级，全局生效）**

```bash
mkdir -p ~/.claude/skills
cp -r safeheron-skill/skills/safeheron ~/.claude/skills/safeheron
```

**Cursor 安装**

Cursor 原生支持 SKILL.md 格式，同时兼容 `.claude/skills/` 路径，因此一次安装可同时支持 Claude Code 和 Cursor。

```bash
# 方式 A：共享安装（Claude Code + Cursor 通用）
mkdir -p .claude/skills
cp -r safeheron-skill/skills/safeheron .claude/skills/safeheron

# 方式 B：Cursor 原生路径（项目级）
mkdir -p .cursor/skills
cp -r safeheron-skill/skills/safeheron .cursor/skills/safeheron

# 方式 C：Cursor 原生路径（用户级）
mkdir -p ~/.cursor/skills
cp -r safeheron-skill/skills/safeheron ~/.cursor/skills/safeheron
```

> ⚠️ 注意：`~/.cursor/skills-cursor/` 是 Cursor 内置的只读目录，**不要**安装到此路径。请使用 `~/.cursor/skills/`。

<figure><img src="/files/ML85eM6J9larQvepJoD8" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/GZ9vy1JRUNUYYRjjYPSN" alt=""><figcaption></figcaption></figure>

#### 2.3 验证安装

安装成功后，在 Claude Code 或 Cursor 中输入以下 prompt 测试：

```plaintext
Use Safeheron-api skill to set up my first API call
```

如果 AI 能够识别 Safeheron SKILL 并开始引导你完成配置，说明安装成功。

![](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8K4nyeZ6paNKYnLb/img/82695822-3808-440e-8a91-77a18f328c64.png)

#### 2.4 Safeheron 平台配置

在编写代码之前，需要先在 Safeheron Console 完成以下配置：

**Step 1：生成 RSA 密钥对**

Safeheron 使用 RSA-4096 进行请求签名和载荷加密。

```bash
# 1. 生成 RSA 4096-bit 私钥
openssl genpkey -out api_private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:4096

# 2. 导出公钥（上传到 Safeheron Console）
openssl rsa -in api_private.pem -out api_public.pem -pubout

# 3. 转换私钥为 PKCS8 格式（Java SDK 要求）
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt \
    -in api_private.pem -out api_pkcs8.pem
```

生成的文件对应关系：

| 文件               | 用途          | 使用方                                    |
| ---------------- | ----------- | -------------------------------------- |
| `api_public.pem` | 公钥          | **上传到 Safeheron Console**              |
| `api_pkcs8.pem`  | PKCS8 编码的私钥 | **SDK 配置中** `**rsaPrivateKey**` **字段** |

提取 Base64 值（去掉 PEM 头尾）：

```bash
# 提取公钥 base64
grep -v "BEGIN\|END" api_public.pem | tr -d '\n'

# 提取私钥 base64
grep -v "BEGIN\|END" api_pkcs8.pem | tr -d '\n'

```

> 🔒 **安全提醒**：私钥文件绝不能提交到版本控制。务必将 `*.pem` 加入 `.gitignore`。

**Step 2：在 Console 配置 API**

1. 登录 **Safeheron Web Console** → **Settings → API**
2. 复制 **Safeheron Platform Public Key**（这是 SDK 配置中的 `safeheronRsaPublicKey`）
3. 创建 API Key：
   * 粘贴你的 RSA 公钥（`api_public.pem` 的 base64 内容）
   * 选择所需权限
   * **添加服务器 IP 到白名单**（必填！未注册的 IP 会被拒绝访问）
4. 保存生成的 API Key 字符串

![](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8K4nyeZ6paNKYnLb/img/4c033a02-f7bc-4718-9203-949f98e4ac61.png)

![](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8K4nyeZ6paNKYnLb/img/5fbfa611-39da-4f57-85fa-127e6d04887e.png)

**Step 3：添加 SDK 依赖**

**Maven**（`pom.xml`）：

```xml
<dependency>
    <groupId>com.safeheron</groupId>
    <artifactId>api-sdk-java</artifactId>
    <version>1.0.12</version>
</dependency>
```

**Gradle**（`build.gradle`）：

```groovy
implementation 'com.safeheron:api-sdk-java:1.0.12'
```

**Step 4：配置凭证注入**

**方式 A：环境变量**

```bash
export SAFEHERON_API_KEY="your-api-key-here"
export SAFEHERON_RSA_PRIVATE_KEY="MIIJQgIBADANBgkqhkiG9w0BAQEFAASC..."
export SAFEHERON_PLATFORM_PUBLIC_KEY="MIICIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."
```

**方式 B：Spring Boot** `**application.yml**`

```yaml
safeheron:
  baseUrl: https://api.safeheron.vip
  apiKey: ${SAFEHERON_API_KEY}
  rsaPrivateKey: ${SAFEHERON_RSA_PRIVATE_KEY}
  safeheronRsaPublicKey: ${SAFEHERON_PLATFORM_PUBLIC_KEY}
  requestTimeout: 20000
```

> ⚠️ 注意 `requestTimeout` 是 **Long** 类型（毫秒），代码中使用 `20000L` 而不是 `20000`。

***

### 3. 接入实例

以下四个场景覆盖了最常见的业务需求：钱包创建、充值归集、提币转账、Webhook 回调。每个场景都包含 **AI Prompt 示例** 和 **生成代码参考**，展示 SKILL 的实际工作方式。

#### 场景一：钱包创建与充值地址分配

**业务需求**：为每个终端用户创建独立的钱包账户，添加 ETH 和 USDT 币种，获取充值地址。

**AI Prompt**

在 Claude Code 或 Cursor 中输入：

```plaintext
使用 Safeheron skill，为一个新用户创建钱包账户，
添加 ETH 和 USDT(ERC20) 两个币种，并获取充值地址。
钱包标记为 DEPOSIT 类型，用于接收用户充值。
```

![](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8K4nyeZ6paNKYnLb/img/b60052df-b41f-43d8-9e55-15a7dce49b99.png)

**生成代码参考**

```java
import com.safeheron.client.api.AccountApiService;
import com.safeheron.client.config.SafeheronConfig;
import com.safeheron.client.request.CreateAccountRequest;
import com.safeheron.client.request.CreateAccountCoinV2Request;
import com.safeheron.client.response.CreateAccountResponse;
import com.safeheron.client.response.CreateAccountCoinV2Response;
import com.safeheron.client.utils.ServiceCreator;
import com.safeheron.client.utils.ServiceExecutor;

import java.util.Arrays;

public class CreateWalletExample {


    public static void main(String[ ] args) throws Exception {


        // ── Step 1: 构建配置 ──
        SafeheronConfig config = SafeheronConfig.builder()
                .baseUrl("https://api.safeheron.vip")
                .apiKey(System.getenv("SAFEHERON_API_KEY"))
                .rsaPrivateKey(System.getenv("SAFEHERON_RSA_PRIVATE_KEY"))
                .safeheronRsaPublicKey(System.getenv("SAFEHERON_PLATFORM_PUBLIC_KEY"))
                .requestTimeout(20000L)
                .build();

        // ── Step 2: 创建 API 服务实例 ──
        AccountApiService accountApi = ServiceCreator.create(
                AccountApiService.class, config);

        // ── Step 3: 创建钱包账户 ──
        CreateAccountRequest createReq = new CreateAccountRequest();
        createReq.setAccountName("user-deposit-001");
        createReq.setHiddenOnUI(true);         // 充值钱包隐藏，不在 Console 显示
        createReq.setAccountTag("DEPOSIT");    // 标记为充值钱包，支持 Auto-Sweep

        CreateAccountResponse createResp = ServiceExecutor.execute(
                accountApi.createAccount(createReq));
        String accountKey = createResp.getAccountKey();
        System.out.println("钱包已创建，accountKey: " + accountKey);
        // ⚠️ 请将 accountKey 与 userId 绑定，持久化到数据库

        // ── Step 4: 添加币种，获取充值地址 ──
        CreateAccountCoinV2Request coinReq = new CreateAccountCoinV2Request();
        coinReq.setAccountKey(accountKey);
        coinReq.setCoinKeyList(Arrays.asList(
                "ETHEREUM_ETH",
                "USDT(ERC20)_ETHEREUM_USDT"
        ));

        CreateAccountCoinV2Response coinResp = ServiceExecutor.execute(
                accountApi.createAccountCoinV2(coinReq));

        System.out.println("已添加币种:");
        for (CreateAccountCoinV2Response.CoinAddress coin : coinResp.getCoinAddressList()) {
            String address = coin.getAddressList().get(0).getAddress();
            System.out.println("  " + coin.getCoinKey() + " → " + address);
            // ⚠️ 将充值地址展示给用户
        }
    }
}
```

**要点说明**

* `hiddenOnUI(true)` —— 充值钱包不需要在 Console UI 中展示，避免界面混乱
* `accountTag("DEPOSIT")` —— 标记为充值钱包后，Auto-Sweep 归集引擎会自动处理该钱包
* 添加 ERC-20 Token（如 USDT）时，ETH 会被自动添加（因为需要 ETH 作为 Gas 费）
* `accountKey` 是钱包的永久唯一标识符，创建后务必保存到数据库

***

#### 场景二：充值检测与资产归集

**业务需求**：检测用户的充值到账，确认后将资产从各个充值钱包归集到平台热钱包。

**AI Prompt**

```plaintext
使用 Safeheron skill，实现充值检测和资产归集：
1. 通过 Webhook 监听充值到账事件
2. 充值确认后，将 USDT 从充值钱包转到热钱包
3. 需要防粉尘攻击过滤，设置最小充值金额
```

![](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8K4nyeZ6paNKYnLb/img/2e4bba84-13e8-4e9e-bb22-3a67de3dd9d2.png)

![](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8K4nyeZ6paNKYnLb/img/14c7c2c3-a16a-4a4a-8cce-4514e153e8ef.png)

**生成代码参考**

**充值检测（Webhook Handler）**

```java
// 在 Webhook 处理器中监听充值事件
public void handleDepositEvent(JsonNode event) {
    String eventType = event.get("eventType").asText();
    if (!"TRANSACTION_STATUS_CHANGED".equals(eventType)) return;

    String txDirection = event.get("transactionDirection").asText();
    String status = event.get("transactionStatus").asText();

    // 只处理充值（INFLOW）且状态为 COMPLETED 的交易
    if (!"INFLOW".equals(txDirection)) return;
    if (!"COMPLETED".equals(status)) return;

    String txKey = event.get("txKey").asText();
    String coinKey = event.get("coinKey").asText();
    String txAmount = event.get("txAmount").asText();
    String destAccountKey = event.get("destinationAccountKey").asText();

    // ── 防粉尘攻击：过滤小额充值 ──
    BigDecimal amount = new BigDecimal(txAmount);
    BigDecimal minDeposit = getMinimumDeposit(coinKey); // 自定义最小充值额
    if (amount.compareTo(minDeposit) < 0) {
        log.info("过滤粉尘充值: {} {} (低于最小额 {})", txAmount, coinKey, minDeposit);
        return;
    }

    // ── 幂等性检查：防止重复入账 ──
    if (depositService.isAlreadyProcessed(txKey)) {
        log.info("充值已处理，跳过: txKey={}", txKey);
        return;
    }

    // ── 入账：更新用户余额 ──
    String userId = walletService.getUserByAccountKey(destAccountKey);
    depositService.creditUser(userId, coinKey, amount, txKey);
    log.info("充值入账成功: userId={}, amount={} {}", userId, txAmount, coinKey);
}
```

**资产归集（Internal Transfer）**

```java
// 将充值钱包的资产归集到热钱包
public void sweepToHotWallet(String depositAccountKey, String coinKey,
                              String amount, String hotWalletAccountKey) {

    // ⚠️ 先生成 customerRefId 并保存到数据库
    String customerRefId = UUID.randomUUID().toString();
    sweepOrderDao.save(new SweepOrder(customerRefId, depositAccountKey,
            hotWalletAccountKey, coinKey, amount, "PENDING"));

    CreateTransactionRequest req = new CreateTransactionRequest();
    req.setCustomerRefId(customerRefId);
    req.setCoinKey(coinKey);
    req.setTxAmount(amount);                           // String 类型
    req.setSourceAccountKey(depositAccountKey);         // 充值钱包
    req.setSourceAccountType("VAULT_ACCOUNT");
    req.setDestinationAccountType("VAULT_ACCOUNT");     // 内部转账
    req.setDestinationAccountKey(hotWalletAccountKey);  // 热钱包
    req.setTxFeeLevel("MIDDLE");

    TxKeyResult resp = ServiceExecutor.execute(
            transactionApi.createTransactions(req));
    sweepOrderDao.updateTxKey(customerRefId, resp.getTxKey(), "SUBMITTED");
}
```

**要点说明**

* **推荐使用 Auto-Sweep**：如果已部署 API Co-Signer，建议在 Console 配置 Auto-Sweep 规则，可实现零代码自动归集
* **Webhook + REST API 轮询**：充值检测必须同时实现两种方式，Webhook 作为主路径，REST API 轮询作为兜底
* **状态不可回退**：如果数据库中交易已经是 `COMPLETED`，收到迟到的 `CONFIRMING` 事件应丢弃
* **粉尘攻击防护**：外部攻击者可能向你的充值地址发送极小金额，务必设置最小充值阈值过滤

***

#### 场景三：提币转账

**业务需求**：处理用户提币请求，从热钱包转出资产到用户指定的外部地址，包含 AML 检查和地址验证。

**AI Prompt**

```plaintext
使用 Safeheron skill，实现用户提币功能：
1. 先验证目标地址格式
2. 执行 AML 风控检查
3. 从热钱包发起转账到外部地址
4. 需要支持超时重试的幂等性设计
```

![](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8K4nyeZ6paNKYnLb/img/008dbd55-bd31-4d0a-8794-39052a7a34af.png)

**生成代码参考**

```java
import com.safeheron.client.api.CoinApiService;
import com.safeheron.client.api.ToolsApiService;
import com.safeheron.client.api.TransactionApiService;
import com.safeheron.client.request.*;
import com.safeheron.client.response.*;
import com.safeheron.client.utils.ServiceCreator;
import com.safeheron.client.utils.ServiceExecutor;

import java.math.BigDecimal;
import java.util.UUID;

public class WithdrawalService {

    private final TransactionApiService transactionApi;
    private final CoinApiService coinApi;
    private final ToolsApiService toolsApi;

    public WithdrawalService(SafeheronConfig config) {
        this.transactionApi = ServiceCreator.create(TransactionApiService.class, config);
        this.coinApi = ServiceCreator.create(CoinApiService.class, config);
        this.toolsApi = ServiceCreator.create(ToolsApiService.class, config);
    }

    public String processWithdrawal(String userId, String coinKey,
                                     String amount, String toAddress,
                                     String hotWalletAccountKey) throws Exception {

        // ── Step 1: 验证目标地址格式 ──
        CheckCoinAddressRequest checkReq = new CheckCoinAddressRequest();
        checkReq.setCoinKey(coinKey);
        checkReq.setAddress(toAddress);
        CheckCoinAddressResponse checkResp = ServiceExecutor.execute(
                coinApi.checkCoinAddress(checkReq));

        if (!checkResp.getAddressValid()) {
            throw new IllegalArgumentException("无效的地址格式: " + toAddress);
        }

        // ── Step 2: AML 风控检查 ──
        AmlScreenRequest amlReq = new AmlScreenRequest();
        amlReq.setAddress(toAddress);
        amlReq.setCoin(coinKey.split("_")[0]); // 提取链标识
        AmlScreenResponse amlResp = ServiceExecutor.execute(
                toolsApi.amlScreen(amlReq));

        if (amlResp.isHighRisk()) {
            throw new SecurityException("AML 检查未通过，高风险地址: " + toAddress);
        }

        // ── Step 3: 生成 customerRefId 并持久化 ──
        // ⚠️ 必须在调用 API 之前保存，确保幂等性
        String customerRefId = UUID.randomUUID().toString();
        withdrawalOrderDao.save(new WithdrawalOrder(
                userId, coinKey, amount, toAddress, customerRefId, "PENDING"));

        // ── Step 4: 发起提币转账 ──
        CreateTransactionRequest req = new CreateTransactionRequest();
        req.setCustomerRefId(customerRefId);
        req.setCoinKey(coinKey);
        req.setTxAmount(amount);                          // ⚠️ String 类型，不要用 float
        req.setSourceAccountKey(hotWalletAccountKey);
        req.setSourceAccountType("VAULT_ACCOUNT");
        req.setDestinationAccountType("ONE_TIME_ADDRESS"); // 外部地址
        req.setDestinationAddress(toAddress);
        req.setTxFeeLevel("MIDDLE");
        req.setFailOnAml(true);                           // 开启 AML 拦截
        req.setFailOnContract(true);                      // 默认阻止合约地址

        try {
            TxKeyResult resp = ServiceExecutor.execute(
                    transactionApi.createTransactions(req));
            withdrawalOrderDao.updateTxKey(customerRefId, resp.getTxKey(), "SUBMITTED");
            return resp.getTxKey();
        } catch (Exception e) {
            if (isDuplicateRefIdError(e)) {
                // Error 9001: customerRefId 已存在，说明之前的请求已成功
                // 查询原始交易，获取 txKey
                OneTransactionsRequest query = new OneTransactionsRequest();
                query.setCustomerRefId(customerRefId);
                OneTransactionsResponse existing = ServiceExecutor.execute(
                        transactionApi.oneTransactions(query));
                withdrawalOrderDao.updateTxKey(customerRefId,
                        existing.getTxKey(), "SUBMITTED");
                return existing.getTxKey();
            }
            throw e; // 其他错误，上层处理
        }
    }

    private boolean isDuplicateRefIdError(Exception e) {
        return e.getMessage() != null && e.getMessage().contains("9001");
    }
}
```

**要点说明**

* **DB-first 模式**：先在数据库创建提币订单（包含 `customerRefId`），再调用 Safeheron API。这样即使网络超时，也能通过相同的 `customerRefId` 重试，Safeheron 会返回已有交易而非创建新交易
* **AML 检查前置**：在调用转账 API 前先通过 `ToolsApiService` 检查目标地址风险等级
* **金额使用 String**：`txAmount` 必须是 String 类型（如 `"0.01"`），应用层使用 `BigDecimal` 计算
* `**failOnAml: true**`：即使前置做了 AML 检查，仍建议保持此选项开启，作为第二道防线
* 对于**经常性转账目标**（如交易所热钱包、合作方地址），应使用**白名单地址**（`WHITELISTING_ACCOUNT`），而非 `ONE_TIME_ADDRESS`

***

#### 场景四：Webhook 回调处理

**业务需求**：实现完整的 Webhook 回调处理服务，支持签名验证、事件路由、幂等处理和安全事件告警。

**AI Prompt**

```plaintext
使用 Safeheron skill，生成 Spring Boot 的 Webhook 处理器：
1. 包含签名验证和 IP 白名单检查
2. 处理交易状态变更事件
3. 处理安全告警事件（非法 IP、策略未匹配等）
4. 实现幂等性和状态不可回退逻辑
```

![](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8K4nyeZ6paNKYnLb/img/b4a7d03a-4a32-4db5-85ad-8d368fb8cf0d.png)

**生成代码参考**

**Webhook 配置类**

```java
@Configuration
public class SafeheronWebhookConfig {

    @Value("${safeheron.webhook.platform-public-key}")
    private String safeheronWebHookRsaPublicKey;

    @Value("${webhook.rsa-private-key}")
    private String webHookRsaPrivateKey;

    @Bean
    public WebhookConverter webhookConverter() {
        return new WebhookConverter(safeheronWebHookRsaPublicKey, webHookRsaPrivateKey);
    }
}
```

**Webhook Controller**

```java
import com.safeheron.client.webhook.WebHook;
import com.safeheron.client.webhook.WebHookBizContent;
import com.safeheron.client.webhook.WebhookConverter;
import com.safeheron.client.webhook.TransactionParam;

@RestController
public class SafeheronWebhookController {

    // Safeheron 出口 IP 白名单
    private static final Set<String> SAFEHERON_IPS = Set.of(
            "18.162.105.64", "18.167.22.59", "18.167.21.182"
    );

    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private WebhookConverter webhookConverter;
    @Resource
    private TransactionService transactionService;
    @Resource
    private AlertService alertService;

    @PostMapping("/safeheron/webhook")
    public WebHookResponse handleWebhook(@RequestBody String rawBody,
                                          HttpServletRequest httpReq) {
        WebHookResponse response = new WebHookResponse();
        response.setCode("200");
        response.setMessage("SUCCESS");

        try {
            // ── Step 1: IP 白名单检查 ──
            String clientIp = httpReq.getRemoteAddr();
            if (!SAFEHERON_IPS.contains(clientIp)) {
                log.warn("拒绝未知 IP 的 Webhook 请求: {}", clientIp);
                return response; // 仍返回 200，避免触发重试风暴
            }

            // ── Step 2: 签名验证 + 解密 ──
            WebHook param = objectMapper.readValue(rawBody, WebHook.class);
            WebHookBizContent content = webhookConverter.convert(param);
            // convert() 内部完成：RSA 签名验证 → AES 密钥解密 → 载荷解密

            // ── Step 3: 事件路由 ──
            String eventType = content.getEventType();
            switch (eventType) {
                // 交易事件
                case "TRANSACTION_STATUS_CHANGED":
                    handleTransactionEvent(content);
                    break;
                case "TRANSACTION_CREATED":
                    log.info("新交易创建: txKey={}", content.getTxKey());
                    break;

                // 安全告警事件
                case "ILLEGAL_IP_REQUEST":
                    alertService.sendAlert("非法 IP 访问 API", content);
                    break;
                case "NO_MATCHING_TRANSACTION_POLICY":
                    alertService.sendAlert("交易无匹配策略", content);
                    break;
                case "GAS_BALANCE_WARNING":
                    alertService.sendAlert("Gas 余额不足", content);
                    break;
                case "AML_KYT_ALERT":
                    alertService.sendAlert("AML/KYT 风险告警", content);
                    break;

                default:
                    log.info("未处理的事件类型: {}", eventType);
            }

        } catch (Exception e) {
            log.error("Webhook 处理异常", e);
        }

        // ⚠️ 始终返回 200，处理逻辑异步执行
        return response;
    }

    private void handleTransactionEvent(WebHookBizContent content) {
        TransactionParam tx = (TransactionParam) content.getEventDetail();
        String txKey = tx.getTxKey();
        String newStatus = tx.getTransactionStatus();

        // ── 幂等性检查 ──
        String currentStatus = transactionService.getStatusByTxKey(txKey);

        // ── 状态不可回退：终态优先 ──
        if (isTerminalStatus(currentStatus)) {
            log.info("交易已处于终态，忽略迟到事件: txKey={}, 当前={}, 收到={}",
                    txKey, currentStatus, newStatus);
            return;
        }

        // ── 更新状态 ──
        transactionService.updateStatus(txKey, newStatus);

        if ("COMPLETED".equals(newStatus) || "SUCCESS".equals(newStatus)) {
            transactionService.onTransactionCompleted(tx);
        } else if ("FAILED".equals(newStatus) || "REJECTED".equals(newStatus)) {
            transactionService.onTransactionFailed(tx);
        }
    }

    private boolean isTerminalStatus(String status) {
        return status != null && Set.of(
                "COMPLETED", "SUCCESS", "FAILED", "REJECTED", "CANCELLED"
        ).contains(status);
    }
}
```

**要点说明**

* **签名验证是强制要求**：SDK 的 `WebhookConverter.convert()` 内部会自动完成 RSA 签名验证和 AES 解密，验证失败会抛出 `SafeheronException`
* **IP 白名单**：Safeheron 的 Webhook 出口 IP 固定为 `18.162.105.64`、`18.167.22.59`、`18.167.21.182`。建议在防火墙/安全组层面限制，而非仅在应用层
* **始终返回 HTTP 200**：即使处理出错也应返回 200。非 200 响应会触发 Safeheron 的重试机制（共 7 次：30s → 1m → 5m → 1h → 12h → 24h）
* **状态不可回退**：由于 Webhook 事件可能乱序到达，必须确保终态（COMPLETED/FAILED/REJECTED/CANCELLED）不会被中间状态覆盖
* **安全事件处理**：务必订阅 `ILLEGAL_IP_REQUEST`、`NO_MATCHING_TRANSACTION_POLICY`、`GAS_BALANCE_WARNING`、`AML_KYT_ALERT` 等安全事件并对接告警系统

***

### 4. 最佳实践

#### 4.1 安全规范（非谈判性要求）

以下安全要求是**强制性的**，所有 Safeheron 集成代码都必须遵守：

**密钥管理**

| 部署环境  | 密钥存储方案                    |
| ----- | ------------------------- |
| AWS 云 | AWS KMS / Secrets Manager |
| GCP 云 | GCP KMS                   |
| 自建机房  | HashiCorp Vault           |
| 本地开发  | 环境变量或项目外的文件（绝不提交到 Git）    |

**绝对禁止**在代码中硬编码 API Key 和 RSA 私钥。

**转账安全**

1. `**customerRefId**` **先行**：在调用任何 Safeheron 创建类 API 之前，先生成 UUID 并保存到数据库。网络超时时使用相同的 ID 重试
2. **地址验证**：调用 `CoinApiService.checkCoinAddress()` 验证地址格式后再操作
3. **AML 前置检查**：每笔出金前通过 `ToolsApiService` 检查目标地址风险
4. **金额精度**：API 中使用 String 类型（`"0.01"`），代码中使用 BigDecimal。**绝不使用** float/double
5. `**failOnAml: true**`：默认开启，仅在明确的业务场景下关闭
6. **白名单优先**：正式的、经常性的转账目标地址应加入白名单；`ONE_TIME_ADDRESS` 仅用于真正的一次性付款

**Co-Signer 安全**

* **禁止盲审批**：每笔交易必须校验 `customerRefId`、金额、目标地址三要素
* Co-Signer 服务必须部署在**隔离的私有网络**中，禁止公网直接访问
* 生产环境的 Co-Signer API Key 必须配置 Callback URL

**Webhook 安全**

* 必须验证 RSA 签名后再处理任何事件
* 生产环境必须使用 HTTPS
* 实现幂等处理，防止重复入账/出金
* 在防火墙层面限制仅接受 Safeheron 出口 IP 的流量
* 始终实现 REST API 轮询作为 Webhook 的兜底方案

#### 4.2 架构设计建议

**审批策略分层**

对于交易所级别的部署，推荐配置分层审批策略：

| 交易金额（24 小时累计）       | 审批方式               |
| ------------------- | ------------------ |
| 单笔 ≤ 10 万 USD       | API Co-Signer 自动审批 |
| 单笔 > 10 万 USD       | 运维团队 2-of-3 审批     |
| 24H 累计 10-50 万 USD  | 运维团队 2-of-3 审批     |
| 24H 累计 50-200 万 USD | 财务团队 2-of-2 审批     |
| 24H 累计 > 200 万 USD  | 高管 1-of-2 审批       |

务必在策略栈底部添加一条**兜底阻断规则**，拦截所有未匹配的交易。

**充值-提币完整架构**

```plaintext
用户充值:
  区块链 → Safeheron 检测 → Webhook 通知 → 入账服务（幂等）→ 用户余额 +
                                                                ↓
                                                        REST API 轮询（兜底）

资产归集:
  充值钱包 → [Gas 补充] → USDT 归集 → 热钱包
  (Auto-Sweep 自动执行，或 API 手动触发)

用户提币:
  前端 → [1. DB 创建订单, status=PENDING] → 返回"已受理"
            ↓
  后台Job → [2. 地址验证 + AML 检查]
          → [3. 调用 Safeheron API, 获取 txKey]
          → [4. 更新订单: txKey, status=SUBMITTED]
            ↓
  Webhook → [5. 接收 TRANSACTION_STATUS_CHANGED]
          → [6. 更新订单状态]
          → [7. 通知用户结果]
```

#### 4.3 使用 SKILL 的高效 Prompt 技巧

以下是一些经过验证的高效 Prompt 示例：

| 需求             | 推荐 Prompt                                                                      |
| -------------- | ------------------------------------------------------------------------------ |
| 快速入门           | `"Use Safeheron skill to set up my first API call"`                            |
| 创建钱包           | `"Generate Java code to create a wallet and add ETH and USDT"`                 |
| 发起转账           | `"Create a transaction to send 0.01 ETH from wallet abc to address 0x1234..."` |
| Spring Boot 配置 | `"Generate Spring Boot configuration class for Safeheron SDK"`                 |
| Webhook 处理     | `"Write a webhook handler that processes incoming transaction events"`         |
| Co-Signer 集成   | `"Help me set up the API Co-Signer approval callback service"`                 |
| 错误排查           | `"My API call returns error 1010 — what's wrong and how do I fix it?"`         |
| 安全审查           | `"Review my Safeheron integration code for security issues"`                   |

**Prompt 技巧**：

1. **明确场景**：告诉 AI 你的业务上下文（如"交易所充值场景"），AI 会自动加入安全检查和最佳实践
2. **指定框架**：如果你使用 Spring Boot，在 prompt 中注明，AI 会生成对应的 Bean 配置和注入代码
3. **提出限制**：说明你的安全要求（如"使用环境变量注入密钥"），AI 会遵循
4. **迭代优化**：先生成基础代码，再让 AI "增加异常处理" 或 "添加 AML 检查"

#### 4.4 测试网环境

Safeheron 支持以下测试网络，建议在正式接入前充分测试：

| 测试网              | coinKey               |
| ---------------- | --------------------- |
| Ethereum Sepolia | `ETH_SEPOLIA`         |
| Bitcoin Testnet  | `BITCOIN_BTC_TESTNET` |
| TRON Shasta      | `TRX_SHASTA`          |

可以通过 Prompt 让 SKILL 生成测试网的代码：

```plaintext
使用 Safeheron skill，在 Ethereum Sepolia 测试网上创建一个测试钱包并发送 0.01 ETH
```

***

### 5. 常见问题与排错

#### 5.1 API 错误码速查

| 错误码          | 含义                | 根因                                              | 解决方案                                   |
| ------------ | ----------------- | ----------------------------------------------- | -------------------------------------- |
| `1010`       | 参数解密失败            | `safeheronRsaPublicKey` 配置错误                    | 从 Console 重新复制 Safeheron 平台公钥          |
| `1012`       | 签名验证失败            | `rsaPrivateKey` 不是 PKCS8 格式，或与 Console 上传的公钥不匹配 | 重新执行 `openssl pkcs8` 转换                |
| `9001`       | customerRefId 已存在 | 重复提交                                            | 查询已有交易而非重新创建                           |
| `9028`       | MPC Sign 策略未配置    | 首次使用 MPC Sign                                   | 联系 Safeheron Support 开通                |
| `Illegal IP` | IP 不在白名单          | 服务器 IP 未注册                                      | 在 Console → API Keys → IP Whitelist 添加 |

#### 5.2 SDK 常见编码错误

**错误：直接调用接口方法**

```java
// ❌ 错误 — 不会执行
CreateAccountResponse resp = accountApi.createAccount(req);

// ✅ 正确 — 必须通过 ServiceExecutor
CreateAccountResponse resp = ServiceExecutor.execute(accountApi.createAccount(req));
```

**错误：类型不匹配**

```java
// ❌ pageSize 和 pageNumber 是 Long 类型
req.setPageSize(10);     // 编译错误
req.setPageNumber(1);    // 编译错误

// ✅ 正确
req.setPageSize(10L);
req.setPageNumber(1L);
```

**错误：金额使用数值类型**

```java
// ❌ 会导致精度丢失
req.setTxAmount(0.1);

// ✅ 正确 — 始终使用 String
req.setTxAmount("0.1");
```

**错误：配置字段混淆**

```java
// ❌ 把自己的公钥当成了平台公钥
.safeheronRsaPublicKey(yourOwnPublicKey)

// ❌ 把平台公钥当成了自己的私钥
.rsaPrivateKey(safeheronPublicKey)

// ✅ 正确
.rsaPrivateKey(yourPKCS8PrivateKey)            // 你的 PKCS8 私钥
.safeheronRsaPublicKey(platformPublicKey)      // Safeheron 平台公钥
```

#### 5.3 使用 SKILL 快速排错

当遇到错误时，可以直接将错误信息发给 AI：

```plaintext
My Safeheron API call returns error 1012 "Signature verification failed".
Here is my config code: [粘贴代码]
Help me fix it.
```

AI 会根据 SKILL 内置的 ERROR\_CODES.md 知识，精准定位问题并给出修复步骤。

![](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8K4nyeZ6paNKYnLb/img/bc6020c1-4244-4dd7-b4a5-0da4b094a678.png)

***

### 6. 附录

#### 6.1 SDK API Service 速查表

| API 领域      | Service 类               | 说明            |
| ----------- | ----------------------- | ------------- |
| 钱包账户        | `AccountApiService`     | 创建/查询钱包、添加币种  |
| 币种管理        | `CoinApiService`        | 查询币种信息、验证地址   |
| 交易          | `TransactionApiService` | 创建/查询/取消/加速交易 |
| MPC 签名      | `MPCSignApiService`     | 原始哈希签名        |
| Web3        | `Web3ApiService`        | EVM 链签名操作     |
| 白名单         | `WhitelistApiService`   | 地址白名单 CRUD    |
| 合规          | `ComplianceApiService`  | AML/KYT 报告查询  |
| Gas Station | `GasApiService`         | Gas 余额和补充记录   |
| 工具          | `ToolsApiService`       | AML 地址风控检查    |

#### 6.2 交易状态流转

```plaintext
SUBMITTED           ← 已提交至 Safeheron
    ↓
WAIT_AUDIT          ← 等待审批
    ↓ (或 → REJECTED)
WAIT_SIGN           ← 等待 MPC 签名
    ↓ (或 → CANCELLED)
BROADCASTING        ← 已广播到链上
    ↓
PENDING             ← 等待链上确认
    ↓
SUCCESS             ← 交易成功 ✅
(或 FAILED)          ← 交易失败 ❌
```

#### 6.3 相关资源

| 资源                     | 链接                                                    |
| ---------------------- | ----------------------------------------------------- |
| Safeheron SKILL GitHub | <https://github.com/absorprofess/safeheron-skill>     |
| Safeheron API 文档       | <https://docs.safeheron.com/api/en.html>              |
| Java SDK GitHub        | <https://github.com/Safeheron/safeheron-api-sdk-java> |
| Safeheron Web Console  | <https://www.safeheron.com>                           |

***
