# Badwhale API Skill

> Run authorized fake-deposit (false-recharge) security tests against a target address via the BadWhale API, and query which chains / networks / tokens are supported. Use when the user wants to test whether an exchange, wallet, or custody system correctly detects forged deposits, or asks what BadWhale supports. Triggers on: "fake deposit", "false recharge", "假充值", "充值检测测试", "deposit detection test", "BadWhale", "which chains/tokens are supported".

- Skill: `slowmist/badwhale-api-skill` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add slowmist/badwhale-api-skill`
- Raw SKILL.md: https://api.skillmd.com/api/skills/slowmist/badwhale-api-skill/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: slowmist (https://skillmd.com/u/slowmist)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/slowmist/badwhale-api-skill

---


# BadWhale API Skill

This skill exposes two capabilities:

| Capability | Description |
|---|---|
| Query supported chains / networks / tokens | Call the BadWhale API for current support and wallet availability |
| Send a fake-deposit test | Submit a test against a given chain, network, token, target address, and amount |

> **Authorized use only.** BadWhale is a commercial security service for testing
> whether a system you **own or are explicitly authorized to assess** correctly
> detects forged ("fake") deposits. Do not use it against third-party systems
> without written authorization. The operator is responsible for ensuring every
> target address belongs to an in-scope, authorized test.

**Language:** instructions below are in English; respond to the user in the
language they wrote in (the user-facing templates can be translated accordingly).

---

## Script invocation

The agent calls the BadWhale API only through `skill/scripts/bw.py`, which reads
credentials from the environment variables `BADWHALE_BASE_URL` and
`BADWHALE_API_KEY`.

> **Script location.** `skill/scripts/bw.py` lives inside **this skill's own
> directory** (the directory that contains this SKILL.md), not the current
> working directory. Resolve the path against the skill directory; if the agent's
> working directory is elsewhere, the bare relative path will not be found.

> **Credentials — important.** Each command runs in a fresh shell, so a separate
> `export` in an earlier step does **not** persist. Always pass credentials
> **inline on the same command line**, e.g.:
>
> ```bash
> BADWHALE_BASE_URL="$BADWHALE_BASE_URL" BADWHALE_API_KEY="$BADWHALE_API_KEY" python skill/scripts/bw.py chains
> ```
>
> If the variables are already exported in the user's shell profile, the inline
> prefix is harmless (it just re-passes the same values). If you only have the
> raw values, substitute them directly. If neither is available, ask the user
> for the API URL and key before running anything. Exit code `10` means the
> script saw no credentials — fix the invocation, do not retry the bare form.
>
> For brevity the commands below are written as `python skill/scripts/bw.py …`,
> but **every** invocation must carry the credential prefix shown above.

| Command | Purpose |
|---|---|
| `python skill/scripts/bw.py health` | Check API connectivity and credentials |
| `python skill/scripts/bw.py chains` | List supported chain / network / token |
| `python skill/scripts/bw.py submit` | Read a JSON body from stdin and submit a batch |
| `python skill/scripts/bw.py poll <batchId>` | Query batch status |

`bw.py` exit codes:

| Exit code | Meaning |
|---|---|
| `0` | Success. |
| `1` | Network-layer error: DNS failure, connection refused, timeout, VPN/intranet unreachable, or URL unreachable — usually no valid HTTP response yet. |
| `2` | API returned HTTP 4xx: the request reached the server but parameters, credentials, address, amount, or idempotency key were rejected — usually fix the request. |
| `3` | API returned HTTP 5xx: the request reached the server but it errored or is unavailable — usually retry or contact the BadWhale team. |
| `10` | Credentials not configured: missing `BADWHALE_BASE_URL` or `BADWHALE_API_KEY`. |
| `11` | Usage error: wrong `bw.py` subcommand or arguments — an internal error in how the agent invoked the script. |

---

## Execution flow (7 steps)

### Step 1: Connectivity + credential check

```bash
BADWHALE_BASE_URL="$BADWHALE_BASE_URL" BADWHALE_API_KEY="$BADWHALE_API_KEY" python skill/scripts/bw.py health
```

- Exit code `10`: missing `BADWHALE_BASE_URL` or `BADWHALE_API_KEY` — set the env vars first, then continue.
- Exit code `1` / `3`: treat as a connection or API-service issue — do not proceed to submit or poll this time.
- Exit code `0`: continue.

### Step 2: Discover available chains and networks (on first run)

```bash
python skill/scripts/bw.py chains
```

### Step 3: Parameter check

| Check | Rule | If not satisfied |
|---|---|---|
| `chain` / `network` / `token` | Must be a supported combination returned by `GET /chains`. | Show the user the valid options and ask them to re-select. |
| `to` | When the token belongs to `bitcoin` / `qtum` / `litecoin` / `dogecoin` / `bitcoincash`, `to` must be a compressed public key (66-char hex, starting with `02` or `03`). | Ask the user to provide a compressed public key. |
| `amount` | If the user gave one, pass it; if not, omit `amount` and let the API use the `defaultAmount` from `GET /chains`. | No need to abort; when confirming before submit, state that the default amount comes from the system. |

Users often say "testnet", "测试网", "test chain" and similar generic words, but
the server's actual network names differ. **Never** submit the literal string
`"testnet"` as a network.

Handling rules:
1. The user said an alias like `"testnet"` / `"测试网"` / `"test"`:
   - The chain has exactly one non-mainnet network → tell the user the real name and confirm: "The testnet for {chain} is `{real_name}`, use this? (yes/no)"
   - Multiple non-mainnet networks → list them and let the user choose.
   - Only mainnet → tell the user this chain has no testnet, and ask whether to switch to mainnet.
2. The user gave a network name that actually exists in `/chains` (e.g. `sepolia` / `nile`) → use it directly.

### Step 4: Final confirmation before submit

Before `POST /batches`, you must show the final request and require an explicit `yes`:

> About to submit the following test, please confirm:
>
> - Chain: `ethereum / mainnet`
> - Token: `ETH`
> - Target: `0x7d7f...64bb`
> - Amount: `0.1` (if using the default, state it comes from the system `defaultAmount` in `GET /chains`)
> - Memo: `customer-001` (omit this line if the user provided no memo)
>
> Note: tests for XRP / DOT / FIL may show a small-amount deposit as successful. As long as it is not a large-amount success, this is generally not abnormal; focus on whether the receiver mistakenly treats a large deposit as successful. (XRP/DOT/FIL only)
>
> Proceed? (yes / no)

### Step 5: Submit the batch

Assemble the request body JSON and pipe it to `bw.py submit` via stdin:

JSON body format:

```json
{
  "clientRequestId": "skill-<unix_ts>-<rand4>",
  "transactions": [
    {
      "chain": "<chain>",
      "network": "<network>",
      "token": "<token>",
      "to": "<target>"
    }
  ]
}
```

Field rules:

- `transactions` holds 1 entry by default; add more only when the user explicitly asks for multiple.
- `chain` / `network` / `token` / `to` must come from user input and be validated against `/chains`.
- Optional fields: `amount`, `memo`.
- `amount`: pass only if the user provided it; otherwise omit it from the JSON so the server uses the token's `defaultAmount`.
- `memo`: pass only if the user explicitly provided it; otherwise omit.
- `clientRequestId`: generate `skill-<unix_ts>-<rand4>`; on `IDEMPOTENCY_CONFLICT`, change it and retry once.
- `callbackUrl`: do not pass it in the conversational submit flow, to avoid introducing webhook behavior.

Example command:

```bash
echo '{
  "clientRequestId": "skill-1715500800-a3f2",
  "transactions": [
    {
      "chain": "ethereum",
      "network": "mainnet",
      "token": "ETH",
      "to": "0x0000000000000000000000000000000000000001",
      "amount": "0.01"
    }
  ]
}' | BADWHALE_BASE_URL="$BADWHALE_BASE_URL" BADWHALE_API_KEY="$BADWHALE_API_KEY" python skill/scripts/bw.py submit
```

### Step 6: Poll status

```bash
python skill/scripts/bw.py poll <batchId>
```

- Poll every 3 seconds, **for at most 5 minutes**. On timeout, stop and ask the user whether to keep polling.
- **Only print a one-line progress update when the batch or task status changes** (`progress: queued → pending → running …`). If nothing changed, stay silent — do not spam.

### Step 7: Render the result

#### Success (batch.status = completed)

> ✅ Test completed
>
> 🔷 ethereum mainnet
> token: ETH　target: 0x7d7f...64bb　amount: 0.1
> Tx: https://etherscan.io/tx/0x9f86d0...
> Tx: https://etherscan.io/tx/0xabc123...
> Tx: https://etherscan.io/tx/0xdef456...

#### Partial (batch.status = partial)

> ⚠️ Partially completed
>
> ✅ ethereum mainnet
> token: ETH　target: 0x7d7f...64bb　amount: 0.1
> Tx: https://etherscan.io/tx/0x9f86d0...
> Tx: https://etherscan.io/tx/0xabc123...
>
> ❌ solana mainnet
> token: SOL　target: 5eykt4...2N9d　amount: 1
> Reason: insufficient balance

#### All failed (batch.status = failed)

> ❌ Test failed
>
> ❌ ethereum mainnet
> token: ETH　target: 0x7d7f...64bb　amount: 0.1
> Reason: NO_WALLET_AVAILABLE (no wallet currently available for this chain/network — contact the BadWhale team)

---

## Information-query scenario (does not enter the submit flow)

### "Which chains / networks / tokens are supported?"

When the user message matches `(support|can use|which|list|available).*(chain|network|token|coin)` (or the Chinese equivalents `(支持|能用|有哪些|哪些|可用).*(链|网络|币|币种|token|chain|network)`) → call `GET /health` first, then `GET /chains` on success; do not create a batch, no pre-submit confirmation needed.

Answer using the "Information-query output / Which chains are supported" template below.

---

## Conversation output templates

### General format conventions (test-result outputs)

1. The first line carries only the overall status (emoji + short conclusion).
2. One block per task, separated by a blank line.
3. The block's first line marks the task status with an emoji: ✅ completed / ❌ failed / 🚫 cancelled.
4. For a single successful task, use 🔷 on the block's first line.
5. Fixed field order: `token` → `target` → `amount`, using full-width colon `：` and full-width space `　` as separators when rendering in Chinese.
6. Each Tx on its own line, showing the full URL (do not use markdown link shorthand).
7. Do not show `batchId`, do not show elapsed time.

### Information-query output

#### Which chains / networks / tokens are supported

Steps:
```bash
python skill/scripts/bw.py health
python skill/scripts/bw.py chains
```

The output must be split into a **Mainnet** section and a **Testnet** section. `network === "mainnet"` goes under Mainnet; every other network goes under Testnet (e.g. `sepolia`, `nile`, `testnet`).

Fixed table columns:

| Chain | Network | Tokens | Availability |
|---|---|---|---|
| `ethereum` | `mainnet` | `ETH`, `USDT`, `USDC` | Available |

Rendering rules:

- `Chain`: the server-returned `chain`.
- `Network`: the server-returned `network`.
- `Tokens`: all `tokens[].token` under that network, comma-separated; do not specially mark the native coin unless the user asked about it.
- `Availability`: `available: true` → `Available`; `available: false` → `Unavailable`.
- Only show data returned by `GET /chains`; never fill in from memory.
- If a section has no results, write `No mainnet configuration available.` or `No testnet configuration available.`
- By default show all returned items, including `available: false` (so the customer knows the service supports it but it is currently not submittable).

Filtering rules:

- User specified a token (e.g. "which networks support USDT") → first filter items where `tokens[].token` equals that token.
- User specified a chain (e.g. "which coins does ethereum support") → first filter by chain.
- Both specified → apply both filters.

Answer template:

> Current BadWhale API support:
>
> **Mainnet**
>
> | Chain | Network | Tokens | Availability |
> |---|---|---|---|
> | `ethereum` | `mainnet` | `ETH`, `USDT`, `USDC` | Available |
> | `tron` | `mainnet` | `TRX`, `USDT` | Unavailable |
>
> **Testnet**
>
> | Chain | Network | Tokens | Availability |
> |---|---|---|---|
> | `ethereum` | `sepolia` | `ETH`, `USDT` | Available |
> | `tron` | `nile` | `TRX`, `USDT` | Available |

This scenario **does not need pre-submit confirmation**, because it creates no batch and triggers no on-chain transaction.

Self-check before output:
1. Does it open with `Current BadWhale API support:` (or its translation)?
2. Are there two sections, **Mainnet** and **Testnet** (with the fallback sentence even if a section is empty)?
3. Is the table header exactly `Chain | Network | Tokens | Availability` (exactly 4 columns)?
4. Are `available: false` rows included (written as `Unavailable`), rather than only listing available items?

If any check fails → re-organize the output; do not send it to the user.

