> 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/product-and-solution/dive-into-safeheron/safeheron-skill.md).

# Safeheron Skill

Instructing AI to write production-ready Safeheron integration code using natural language — A complete guide from zero to deployment.

***

### 1. Product Overview

#### 1.1 What Is Safeheron Skill

**Safeheron Skill** is an AI Skill plugin for **Claude Code** and **Cursor** **IDEs**. Once installed, developers can describe their requirements in **natural language** and have the AI generate production-ready Java SDK code — no more digging through Safeheron API documentation page by page.

Core capabilities include:

* **Code Generation** — Simply describe your business scenario, and the AI will automatically generate SDK-compliant Java code, complete with correct imports, exception handling, and security validations.
* **Full API Coverage** — Covers all APIs, including wallet management, transfers, MPC Sign, Web3 Sign, webhook callbacks, whitelists, Gas Station, AML/KYT compliance checks, and Co-Signer approvals.
* **Debugging & Troubleshooting** — Input error codes or exception messages, and the AI will directly pinpoint the root cause and provide a solution.
* **Built-In Security & Compliance** — Every code snippet generated strictly adheres to Safeheron's security best practices, including key management, idempotency, and AML checks.

#### 1.2 Who Is This For

| **Role**            | **Use Case**                                                                                      |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| Backend Developer   | Quickly generate SDK integration code, minimizing the time spent on documentation.                |
| Technical Architect | Explore the boundaries of Safeheron's APIs to design comprehensive system architecture solutions. |
| Security Engineer   | Review code security and gain deep insights into MPC self-custody security requirements.          |
| Product Manager     | Rapidly discover supported business scenarios and the full scope of Safeheron's capabilities.     |

#### 1.3 Key Concepts

Before diving into the examples, here are the foundational concepts you need to know:

**MPC Self-Custody**: Safeheron utilizes a 3-of-3 MPC (Secure Multi-Party Computation) architecture. The complete private key never exists on any single device. Instead, three key shards are distributed between your team's local environment and the Safeheron cloud, which collaboratively sign transactions.

**Wallet Account**: A Safeheron wallet account is a **omnichain** **wallet** — each account can hold addresses across multiple blockchains (ETH, BTC, USDT, etc.). As a best practice, we recommend **mapping one end-user to exactly one Safeheron Wallet Account (a 1:1 relationship)**.

**SDK Calling Pattern**: The Java SDK follows a strict three-step pattern:

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

Every API call **must** be wrapped with `ServiceExecutor.execute()`. Calling interface methods directly will fail silently.

**Transaction Lifecycle**:

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

#### 1.4 Built-In Knowledge Base

The Skill ships with 20 comprehensive reference documents covering every aspect of the Safeheron API:

| **Category**      | **Document**                 | **Description**                              |
| ----------------- | ---------------------------- | -------------------------------------------- |
| Getting Started   | GETTING\_STARTED.md          | Zero to first API call                       |
| SDK Setup         | SDK\_SETUP.md                | Maven/Gradle config, Spring Boot integration |
| Authentication    | AUTH.md                      | RSA + AES encryption and signing flow        |
| Wallets           | WALLET\_API.md               | Wallet creation, querying, coin management   |
| Transactions      | TRANSACTION\_API.md          | Transfers, queries, cancellation, speed-up   |
| MPC Signing       | MPC\_SIGN\_API.md            | Raw hash signing                             |
| Web3              | WEB3\_API.md                 | EVM chain signing operations                 |
| Webhooks          | WEBHOOK.md                   | Event callback handling                      |
| Whitelists        | WHITELIST\_API.md            | Address whitelist management                 |
| Compliance        | COMPLIANCE\_API.md           | AML/KYT reporting                            |
| Tools             | TOOLS\_API.md                | AML address risk screening                   |
| Gas               | GAS\_API.md                  | Gas Station status queries                   |
| Co-Signer         | COSIGNER.md                  | Automated approval callbacks                 |
| Security          | SECURITY\_BEST\_PRACTICES.md | Mandatory security coding standards          |
| Checklist         | SECURITY\_CHECKLIST.md       | Pre-launch security checklist                |
| Policy            | POLICY\_STRATEGY.md          | Approval policy configuration                |
| Business Patterns | BUSINESS\_PATTERNS.md        | Deposit / withdrawal / sweep architecture    |
| Error Codes       | ERROR\_CODES.md              | Common error troubleshooting                 |
| FAQ               | FAQ.md                       | Real-world Q\&A                              |
| Coins             | COIN\_API.md                 | Coin queries, address validation             |

***

### 2. Setup & Installation

#### 2.1 Prerequisites

* Java 8+
* Maven 3.x or Gradle 7+
* OpenSSL (pre-installed on macOS/Linux; use Git Bash or WSL on Windows)
* Safeheron Web Console account (<https://www.safeheron.com>)
* Claude Code or Cursor IDE

#### 2.2 Install the Skill

**Claude Code**

**Option 1: Plugin install (recommended)**

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

**Option 2: Manual install (project-level)**

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

**Option 3: Manual install (user-level, applies globally)**

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

**Cursor**

Cursor natively supports the SKILL.md format and is also compatible with `.claude/skills/`, so a single install covers both IDEs.

```bash
# Option A: Shared install (works for both Claude Code AND Cursor)
mkdir -p .claude/skills
cp -r safeheron-skill/skills/safeheron .claude/skills/safeheron

# Option B: Cursor native path (project-level)
mkdir -p .cursor/skills
cp -r safeheron-skill/skills/safeheron .cursor/skills/safeheron

# Option C: Cursor native path (user-level)
mkdir -p ~/.cursor/skills
cp -r safeheron-skill/skills/safeheron ~/.cursor/skills/safeheron
```

> **Note:** `~/.cursor/skills-cursor/` is Cursor's built-in read-only directory — do **not** install there. Use `~/.cursor/skills/` instead.

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2FlzeQJHmxMF7OMBwxE3Vl%2Fimage.png?alt=media&amp;token=62afcb8f-33cb-4072-b52e-9218a2a6f617" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2FEN6W71RkXbgFiW7MDOLq%2Fimage.png?alt=media&amp;token=a540faac-5f9d-45de-8bd9-09817811090d" alt=""><figcaption></figcaption></figure>

#### 2.3 Verify Installation

After installation, enter the following prompt in Claude Code or Cursor to test:

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

If the AI recognizes the Safeheron SKILL and begins guiding you through the setup, the installation was successful.

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2FNFMHntb4Mx7aSDnIdVok%2Fimage.png?alt=media&amp;token=179d49f3-153b-4489-a795-c11c5cd8f982" alt=""><figcaption></figcaption></figure>

#### 2.4 Safeheron Platform Configuration

Before writing any code, complete the following configuration in the Safeheron Console:

**Step 1: Generate an RSA Key Pair**

Safeheron uses RSA-4096 for request signing and payload encryption.

```bash
# 1. Generate RSA 4096-bit private key
openssl genpkey -out api_private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:4096

# 2. Export public key (upload to Safeheron Console)
openssl rsa -in api_private.pem -out api_public.pem -pubout

# 3. Convert private key to PKCS8 format (required by the Java SDK)
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt \
    -in api_private.pem -out api_pkcs8.pem
```

Generated files and their purposes:

| **File**         | **Purpose**               | **Used By**                                  |
| ---------------- | ------------------------- | -------------------------------------------- |
| `api_public.pem` | Public key                | **Upload to Safeheron Console**              |
| `api_pkcs8.pem`  | PKCS8-encoded private key | **SDK config** `**rsaPrivateKey**` **field** |

Extract the Base64 values (strip PEM headers/footers):

```bash
# Extract public key base64
grep -v "BEGIN\|END" api_public.pem | tr -d '\n'

# Extract private key base64
grep -v "BEGIN\|END" api_pkcs8.pem | tr -d '\n'
```

> **Security:** Never commit private key files to version control. Always add `*.pem` to `.gitignore`.

**Step 2: Configure in the Console**

1. Log in to **Safeheron Web Console** → **Settings → API**
2. Copy the **Safeheron Platform Public Key** (this is the `safeheronRsaPublicKey` in your SDK config)
3. Create an API Key:
   * Paste your RSA public key (base64 content from `api_public.pem`)
   * Select required permissions
   * **Add your server IP to the whitelist** (mandatory — unregistered IPs are rejected)
4. Save the generated API Key string

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2Fn0Iwxduk5fGJvfwzZ8HE%2Fimage.png?alt=media&amp;token=7f9ab915-5bf8-4f04-8592-b6bbdb06cc40" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2FVIFahqmJ6ecUcwlQm8Iw%2Fimage.png?alt=media&amp;token=49b766fe-1850-441e-b13a-8e7a06f0ad6c" alt=""><figcaption></figcaption></figure>

**Step 3: Add SDK Dependency**

**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.9'
```

**Step 4: Inject Credentials**

**Option A: Environment variables (recommended for production)**

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

**Option 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
```

> **Note:** `requestTimeout` is a **Long** (milliseconds). Use `20000L` in code, not `20000`.

***

### 3. Integration Examples

The following four examples cover the most common business scenarios: wallet creation, deposit detection with sweeping, withdrawal processing, and webhook handling. Each includes an **AI Prompt example** and **generated code reference** to demonstrate how the SKILL works in practice.

#### Example 1: Wallet Creation & Deposit Address Allocation

**Business requirement**: Create an independent wallet for each end-user, add ETH and USDT coins, and retrieve deposit addresses.

**AI Prompt**

Enter in Claude Code or Cursor:

```plaintext
Use Safeheron skill to create a wallet account for a new user,
add ETH and USDT(ERC20), and get the deposit addresses.
Tag it as DEPOSIT for auto-sweep.
```

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2FORVEU71y81VDhEOMvdOw%2Fimage.png?alt=media&amp;token=57f4d59c-1be3-4fdf-8fee-3cf949185455" alt=""><figcaption></figcaption></figure>

**Generated Code Reference**

```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: Build config ──
        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: Create API service instance ──
        AccountApiService accountApi = ServiceCreator.create(
                AccountApiService.class, config);

        // ── Step 3: Create wallet account ──
        CreateAccountRequest createReq = new CreateAccountRequest();
        createReq.setAccountName("user-deposit-001");
        createReq.setHiddenOnUI(true);         // Hide deposit wallets from Console UI
        createReq.setAccountTag("DEPOSIT");    // Tag for Auto-Sweep eligibility

        CreateAccountResponse createResp = ServiceExecutor.execute(
                accountApi.createAccount(createReq));
        String accountKey = createResp.getAccountKey();
        System.out.println("Wallet created. accountKey: " + accountKey);
        // IMPORTANT: Persist accountKey ↔ userId binding in your database

        // ── Step 4: Add coins, get deposit addresses ──
        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("Coins added:");
        for (CreateAccountCoinV2Response.CoinAddress coin : coinResp.getCoinAddressList()) {
            String address = coin.getAddressList().get(0).getAddress();
            System.out.println("  " + coin.getCoinKey() + " -> " + address);
            // Display this address to the user for deposits
        }
    }
}
```

**Key Points**

* `hiddenOnUI(true)` — Deposit wallets don't need to appear in the Console UI; keeps things clean
* `accountTag("DEPOSIT")` — Required for Auto-Sweep; the sweep engine only processes wallets tagged `DEPOSIT`
* Adding an ERC-20 token (e.g. USDT) automatically adds ETH as well (needed for gas fees)
* `accountKey` is the wallet's permanent unique identifier — always persist it to your database after creation

***

#### Example 2: Deposit Detection & Asset Sweeping

**Business requirement**: Detect when users deposit funds, credit their accounts upon confirmation, then sweep assets from individual deposit wallets into the platform's hot wallet.

**AI Prompt**

```plaintext
Use Safeheron skill to implement deposit detection and asset sweeping:
1. Listen for deposit events via webhook
2. After confirmation, sweep USDT from deposit wallets to the hot wallet
3. Include dust attack filtering with a minimum deposit threshold
```

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2FZVIHhAvZ9gRI3p6FV9di%2Fimage.png?alt=media&amp;token=ebdefe48-714f-43da-8587-4a03b6026a0d" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2FOeDDArBzNu9PVNjI9lPN%2Fimage.png?alt=media&amp;token=dd57873e-97ab-47f3-9915-9d97bfaa5be2" alt=""><figcaption></figcaption></figure>

**Generated Code Reference**

**Deposit Detection (Webhook Handler)**

```java
// Inside your webhook event processor
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();

    // Only process completed inflow (deposit) transactions
    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();

    // ── Dust attack filtering ──
    BigDecimal amount = new BigDecimal(txAmount);
    BigDecimal minDeposit = getMinimumDeposit(coinKey);
    if (amount.compareTo(minDeposit) < 0) {
        log.info("Filtering dust deposit: {} {} (below min {})", txAmount, coinKey, minDeposit);
        return;
    }

    // ── Idempotency check ──
    if (depositService.isAlreadyProcessed(txKey)) {
        log.info("Deposit already processed, skipping: txKey={}", txKey);
        return;
    }

    // ── Credit user account ──
    String userId = walletService.getUserByAccountKey(destAccountKey);
    depositService.creditUser(userId, coinKey, amount, txKey);
    log.info("Deposit credited: userId={}, amount={} {}", userId, txAmount, coinKey);
}
```

**Asset Sweeping (Internal Transfer)**

```java
// Sweep funds from a deposit wallet to the hot wallet
public void sweepToHotWallet(String depositAccountKey, String coinKey,
                              String amount, String hotWalletAccountKey) {

    // IMPORTANT: Generate and persist customerRefId BEFORE calling the API
    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 type
    req.setSourceAccountKey(depositAccountKey);          // Deposit wallet
    req.setSourceAccountType("VAULT_ACCOUNT");
    req.setDestinationAccountType("VAULT_ACCOUNT");      // Internal transfer
    req.setDestinationAccountKey(hotWalletAccountKey);   // Hot wallet
    req.setTxFeeLevel("MIDDLE");

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

**Key Points**

* **Prefer Auto-Sweep**: If you have an API Co-Signer deployed, configure Auto-Sweep rules in the Console for zero-code automated sweeping
* **Webhook + REST API polling**: Deposit detection must implement both — webhook as primary, REST API polling as fallback
* **No status rollback**: If a transaction is already `COMPLETED` in your database, discard any late-arriving `CONFIRMING` event
* **Dust attack protection**: External actors may send tiny amounts to your deposit addresses to pollute your webhook stream — always set a minimum deposit threshold

***

#### Example 3: Withdrawal Processing

**Business requirement**: Process user withdrawal requests from the hot wallet to external addresses, including AML screening and address validation.

**AI Prompt**

```plaintext
Use Safeheron skill to implement user withdrawals:
1. Validate the destination address format
2. Run AML risk screening
3. Transfer from hot wallet to external address
4. Support idempotent retry on timeout
```

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2FeMOztsQm6HQ0253HMV2F%2Fimage.png?alt=media&amp;token=a44b0f2a-463b-46aa-8d17-266a74befea2" alt=""><figcaption></figcaption></figure>

**Generated Code Reference**

```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: Validate destination address format ──
        CheckCoinAddressRequest checkReq = new CheckCoinAddressRequest();
        checkReq.setCoinKey(coinKey);
        checkReq.setAddress(toAddress);
        CheckCoinAddressResponse checkResp = ServiceExecutor.execute(
                coinApi.checkCoinAddress(checkReq));

        if (!checkResp.getAddressValid()) {
            throw new IllegalArgumentException("Invalid address format: " + toAddress);
        }

        // ── Step 2: AML risk screening ──
        AmlScreenRequest amlReq = new AmlScreenRequest();
        amlReq.setAddress(toAddress);
        amlReq.setCoin(coinKey.split("_")[0]); // Extract chain identifier
        AmlScreenResponse amlResp = ServiceExecutor.execute(
                toolsApi.amlScreen(amlReq));

        if (amlResp.isHighRisk()) {
            throw new SecurityException("AML check failed — high-risk address: " + toAddress);
        }

        // ── Step 3: Generate and persist customerRefId BEFORE calling API ──
        String customerRefId = UUID.randomUUID().toString();
        withdrawalOrderDao.save(new WithdrawalOrder(
                userId, coinKey, amount, toAddress, customerRefId, "PENDING"));

        // ── Step 4: Submit withdrawal ──
        CreateTransactionRequest req = new CreateTransactionRequest();
        req.setCustomerRefId(customerRefId);
        req.setCoinKey(coinKey);
        req.setTxAmount(amount);                           // String type — never float
        req.setSourceAccountKey(hotWalletAccountKey);
        req.setSourceAccountType("VAULT_ACCOUNT");
        req.setDestinationAccountType("ONE_TIME_ADDRESS");  // External address
        req.setDestinationAddress(toAddress);
        req.setTxFeeLevel("MIDDLE");
        req.setFailOnAml(true);                            // Enable AML blocking
        req.setFailOnContract(true);                       // Block contract addresses by default

        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 already exists — previous request succeeded
                // Query the original transaction to get the 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; // Other errors — propagate to caller
        }
    }

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

**Key Points**

* **DB-first pattern**: Create the withdrawal order in your database (with `customerRefId`) before calling the Safeheron API. On timeout, retry with the same ID — Safeheron returns the existing transaction rather than creating a new one
* **Pre-flight AML check**: Screen the destination address via `ToolsApiService` before submitting the transaction
* **String amounts**: `txAmount` must be a String (e.g. `"0.01"`); use `BigDecimal` for application-side calculations. **Never** use float/double
* `**failOnAml: true**`: Even with pre-flight AML checks, keep this enabled as a second line of defense
* For **recurring transfer destinations** (exchange hot wallets, partner addresses), use **whitelisted addresses** (`WHITELISTING_ACCOUNT`) instead of `ONE_TIME_ADDRESS`

***

#### Example 4: Webhook Handler

**Business requirement**: Implement a complete webhook handler with signature verification, event routing, idempotency, and security event alerting.

**AI Prompt**

```plaintext
Use Safeheron skill to generate a Spring Boot webhook handler:
1. Include signature verification and IP whitelist checks
2. Handle transaction status change events
3. Handle security alert events (illegal IP, unmatched policy, etc.)
4. Implement idempotency and no-status-rollback logic
```

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2FawbyzKIcf2ZmnUVIdl1D%2Fimage.png?alt=media&amp;token=3b1e29b3-c9c4-44cf-b40b-70fbcc95ed9b" alt=""><figcaption></figcaption></figure>

**Generated Code Reference**

**Webhook Configuration**

```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 egress IP whitelist
    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 whitelist check ──
            String clientIp = httpReq.getRemoteAddr();
            if (!SAFEHERON_IPS.contains(clientIp)) {
                log.warn("Rejected webhook from unknown IP: {}", clientIp);
                return response; // Still return 200 to avoid retry storms
            }

            // ── Step 2: Signature verification + decryption ──
            WebHook param = objectMapper.readValue(rawBody, WebHook.class);
            WebHookBizContent content = webhookConverter.convert(param);
            // convert() internally handles: RSA signature verification → AES key decryption → payload decryption

            // ── Step 3: Event routing ──
            String eventType = content.getEventType();
            switch (eventType) {
                // Transaction events
                case "TRANSACTION_STATUS_CHANGED":
                    handleTransactionEvent(content);
                    break;
                case "TRANSACTION_CREATED":
                    log.info("New transaction created: txKey={}", content.getTxKey());
                    break;

                // Security alert events
                case "ILLEGAL_IP_REQUEST":
                    alertService.sendAlert("Illegal IP accessing API", content);
                    break;
                case "NO_MATCHING_TRANSACTION_POLICY":
                    alertService.sendAlert("Transaction has no matching policy", content);
                    break;
                case "GAS_BALANCE_WARNING":
                    alertService.sendAlert("Gas balance low", content);
                    break;
                case "AML_KYT_ALERT":
                    alertService.sendAlert("AML/KYT risk alert", content);
                    break;

                default:
                    log.info("Unhandled event type: {}", eventType);
            }

        } catch (Exception e) {
            log.error("Webhook processing error", e);
        }

        // IMPORTANT: Always return 200 — offload processing to async workers
        return response;
    }

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

        // ── Idempotency check ──
        String currentStatus = transactionService.getStatusByTxKey(txKey);

        // ── No status rollback: terminal state wins ──
        if (isTerminalStatus(currentStatus)) {
            log.info("Transaction already terminal, ignoring late event: txKey={}, current={}, received={}",
                    txKey, currentStatus, newStatus);
            return;
        }

        // ── Update status ──
        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);
    }
}
```

**Key Points**

* **Signature verification is mandatory**: The SDK's `WebhookConverter.convert()` internally handles RSA signature verification and AES decryption; it throws `SafeheronException` on failure
* **IP whitelist**: Safeheron's webhook egress IPs are fixed: `18.162.105.64`, `18.167.22.59`, `18.167.21.182`. Enforce this at the firewall/security group level, not just in application code
* **Always return HTTP 200**: Even if processing fails. Non-200 responses trigger Safeheron's retry mechanism (7 attempts: 30s → 1m → 5m → 1h → 12h → 24h)
* **No status rollback**: Webhook events may arrive out of order. Terminal states (COMPLETED/FAILED/REJECTED/CANCELLED) must never be overwritten by intermediate states
* **Security event handling**: Always subscribe to `ILLEGAL_IP_REQUEST`, `NO_MATCHING_TRANSACTION_POLICY`, `GAS_BALANCE_WARNING`, and `AML_KYT_ALERT` events and route them to your alerting system

***

### 4. Best Practices

#### 4.1 Security Requirements (Non-Negotiable)

These security requirements are **mandatory** — all Safeheron integration code must comply:

**Key Management**

| Deployment Target | Required Solution                                                                     |
| ----------------- | ------------------------------------------------------------------------------------- |
| AWS Cloud         | AWS KMS / Secrets Manager                                                             |
| GCP Cloud         | GCP KMS                                                                               |
| Self-Hosted       | HashiCorp Vault                                                                       |
| Local Development | Environment variables or files outside the project directory (never committed to Git) |

**Never** hardcode API Keys or RSA private keys in source code.

**Transfer Security**

1. `**customerRefId**` **first**: Generate a UUID and save it to your database before calling any Safeheron create API. On timeout, retry with the same ID
2. **Address validation**: Call `CoinApiService.checkCoinAddress()` to verify the address format before any operation
3. **Pre-flight AML screening**: Screen every destination address via `ToolsApiService` before outbound transfers
4. **Amount precision**: Use String in API calls (`"0.01"`), BigDecimal in application logic. **Never** use float/double
5. `**failOnAml: true**`: Keep this enabled by default; only disable with explicit business justification
6. **Whitelist first**: Recurring transfer destinations should be whitelisted; `ONE_TIME_ADDRESS` is only for genuinely one-off payments

**Co-Signer Security**

* **No blind approval**: Every transaction must be validated against `customerRefId`, amount, and destination address
* Co-Signer must be deployed in an **isolated private network** with no public internet inbound access
* Production Co-Signer API Keys must have a Callback URL configured

**Webhook Security**

* Verify RSA signature before processing any event
* Production endpoints must use HTTPS
* Implement idempotent handlers to prevent double-crediting or double-processing
* Restrict inbound traffic to Safeheron egress IPs at the firewall level
* Always implement REST API polling as a fallback alongside webhooks

#### 4.2 Architecture Recommendations

**Tiered Approval Strategy**

For exchange-grade deployments, configure layered approval policies:

| Transaction Amount (24H cumulative) | Approval Method            |
| ----------------------------------- | -------------------------- |
| Single tx ≤ $100,000                | API Co-Signer auto-approve |
| Single tx > $100,000                | Ops team 2-of-3            |
| 24H total $100K–$500K               | Ops team 2-of-3            |
| 24H total $500K–$2M                 | Finance team 2-of-2        |
| 24H total > $2M                     | Executive 1-of-2           |

Always add a **catch-all blocking rule** at the bottom of the policy stack to intercept unmatched transactions.

**Complete Deposit–Withdrawal Architecture**

```plaintext
User Deposit:
  Blockchain → Safeheron detection → Webhook notification → Credit service (idempotent) → User balance +
                                                                  ↓
                                                          REST API polling (fallback)

Asset Sweep:
  Deposit wallet → [Gas top-up] → Sweep USDT → Hot wallet
  (Auto-Sweep handles automatically, or trigger via API)

User Withdrawal:
  Frontend → [1. Create order in DB, status=PENDING] → Return "request received"
               ↓
  Background Job → [2. Address validation + AML screening]
                 → [3. Call Safeheron API, get txKey]
                 → [4. Update order: txKey, status=SUBMITTED]
               ↓
  Webhook → [5. Receive TRANSACTION_STATUS_CHANGED]
          → [6. Update order status]
          → [7. Notify user of result]
```

#### 4.3 Effective Prompt Techniques

Here are some proven, high-impact prompts for use with the SKILL:

| Need               | Recommended Prompt                                                             |
| ------------------ | ------------------------------------------------------------------------------ |
| Quick start        | `"Use Safeheron skill to set up my first API call"`                            |
| Create wallet      | `"Generate Java code to create a wallet and add ETH and USDT"`                 |
| Send transaction   | `"Create a transaction to send 0.01 ETH from wallet abc to address 0x1234..."` |
| Spring Boot config | `"Generate Spring Boot configuration class for Safeheron SDK"`                 |
| Webhook handler    | `"Write a webhook handler that processes incoming transaction events"`         |
| Co-Signer setup    | `"Help me set up the API Co-Signer approval callback service"`                 |
| Error debugging    | `"My API call returns error 1010 — what's wrong and how do I fix it?"`         |
| Security review    | `"Review my Safeheron integration code for security issues"`                   |

**Prompt tips**:

1. **Specify context**: Tell the AI your business scenario (e.g. "exchange deposit flow") and it will automatically include security checks and best practices
2. **Name your framework**: If you use Spring Boot, mention it in the prompt — the AI will generate corresponding Bean configurations and dependency injection
3. **State constraints**: Mention security requirements (e.g. "inject keys via environment variables") and the AI will comply
4. **Iterate**: Start with basic code, then ask the AI to "add exception handling" or "add AML screening"

#### 4.4 Testnet Environment

Safeheron supports the following test networks — always test thoroughly before going live:

| Test Network     | coinKey               |
| ---------------- | --------------------- |
| Ethereum Sepolia | `ETH_SEPOLIA`         |
| Bitcoin Testnet  | `BITCOIN_BTC_TESTNET` |
| TRON Shasta      | `TRX_SHASTA`          |

Use a prompt like this to generate testnet code:

```plaintext
Use Safeheron skill to create a test wallet on Ethereum Sepolia and send 0.01 ETH
```

***

### 5. Troubleshooting

#### 5.1 API Error Code Quick Reference

| Code         | Message                        | Root Cause                                                                      | Resolution                                                   |
| ------------ | ------------------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `1010`       | Parameter decryption failed    | Wrong `safeheronRsaPublicKey` in config                                         | Re-copy the Safeheron Platform Public Key from Console       |
| `1012`       | Signature verification failed  | `rsaPrivateKey` not in PKCS8 format, or doesn't match the public key in Console | Re-run `openssl pkcs8` conversion                            |
| `9001`       | customerRefId already exists   | Duplicate submission                                                            | Query the existing transaction instead of creating a new one |
| `9028`       | MPC Sign policy not configured | First-time MPC Sign usage                                                       | Contact Safeheron Support to activate                        |
| `Illegal IP` | IP not whitelisted             | Server IP not registered                                                        | Add IP in Console → API Keys → IP Whitelist                  |

#### 5.2 Common SDK Coding Errors

**Error: Calling interface methods directly**

```java
// WRONG — will NOT work
CreateAccountResponse resp = accountApi.createAccount(req);

// CORRECT — must use ServiceExecutor
CreateAccountResponse resp = ServiceExecutor.execute(accountApi.createAccount(req));
```

**Error: Wrong data types**

```java
// WRONG — pageSize and pageNumber are Long
req.setPageSize(10);     // Compile error
req.setPageNumber(1);    // Compile error

// CORRECT
req.setPageSize(10L);
req.setPageNumber(1L);
```

**Error: Numeric amount types**

```java
// WRONG — causes precision loss
req.setTxAmount(0.1);

// CORRECT — always use String
req.setTxAmount("0.1");
```

**Error: Swapping config fields**

```java
// WRONG — your own public key in the platform field
.safeheronRsaPublicKey(yourOwnPublicKey)

// WRONG — platform key in the private key field
.rsaPrivateKey(safeheronPublicKey)

// CORRECT
.rsaPrivateKey(yourPKCS8PrivateKey)            // Your PKCS8 private key
.safeheronRsaPublicKey(platformPublicKey)      // Safeheron's platform public key
```

#### 5.3 Using the SKILL for Quick Debugging

When you encounter an error, paste it directly to the AI:

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

The AI will use the SKILL's built-in ERROR\_CODES.md knowledge to pinpoint the issue and provide step-by-step fixes.

<figure><img src="https://1144952454-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRN8kRfl2uPxOwStzznzu%2Fuploads%2FrdcYrc9gzda8SQgRrW3I%2Fimage.png?alt=media&amp;token=bd31b96c-8237-43b3-a561-97c1aa461a6a" alt=""><figcaption></figcaption></figure>

***

### 6. Appendix

#### 6.1 SDK API Service Quick Reference

| API Area        | Service Class           | Description                               |
| --------------- | ----------------------- | ----------------------------------------- |
| Wallet Account  | `AccountApiService`     | Create/query wallets, add coins           |
| Coin Management | `CoinApiService`        | Query coin info, validate addresses       |
| Transaction     | `TransactionApiService` | Create/query/cancel/speed-up transactions |
| MPC Signing     | `MPCSignApiService`     | Raw hash signing                          |
| Web3            | `Web3ApiService`        | EVM chain signing operations              |
| Whitelist       | `WhitelistApiService`   | Address whitelist CRUD                    |
| Compliance      | `ComplianceApiService`  | AML/KYT report queries                    |
| Gas Station     | `GasApiService`         | Gas balance and refill records            |
| Tools           | `ToolsApiService`       | AML address risk screening                |

#### 6.2 Transaction Status Flow

```plaintext
SUBMITTED           ← Received by Safeheron
    ↓
WAIT_AUDIT          ← Pending approval
    ↓ (or → REJECTED)
WAIT_SIGN           ← Pending MPC signing
    ↓ (or → CANCELLED)
BROADCASTING        ← Broadcast to blockchain
    ↓
PENDING             ← Awaiting on-chain confirmation
    ↓
SUCCESS             ← Transaction confirmed ✅
(or FAILED)          ← Transaction failed ❌
```

#### 6.3 Resources

| Resource               | Link                                                  |
| ---------------------- | ----------------------------------------------------- |
| Safeheron SKILL GitHub | <https://github.com/absorprofess/safeheron-skill>     |
| Safeheron API Docs     | <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>                           |
