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.pylives 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
exportin an earlier step does not persist. Always pass credentials inline on the same command line, e.g.:BADWHALE_BASE_URL="$BADWHALE_BASE_URL" BADWHALE_API_KEY="$BADWHALE_API_KEY" python skill/scripts/bw.py chainsIf 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
10means 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
BADWHALE_BASE_URL="$BADWHALE_BASE_URL" BADWHALE_API_KEY="$BADWHALE_API_KEY" python skill/scripts/bw.py health
- Exit code
10: missingBADWHALE_BASE_URLorBADWHALE_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)
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:
- 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.
- The chain has exactly one non-mainnet network → tell the user the real name and confirm: "The testnet for {chain} is
- 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 systemdefaultAmountinGET /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:
{
"clientRequestId": "skill-<unix_ts>-<rand4>",
"transactions": [
{
"chain": "<chain>",
"network": "<network>",
"token": "<token>",
"to": "<target>"
}
]
}
Field rules:
transactionsholds 1 entry by default; add more only when the user explicitly asks for multiple.chain/network/token/tomust 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'sdefaultAmount.memo: pass only if the user explicitly provided it; otherwise omit.clientRequestId: generateskill-<unix_ts>-<rand4>; onIDEMPOTENCY_CONFLICT, change it and retry once.callbackUrl: do not pass it in the conversational submit flow, to avoid introducing webhook behavior.
Example command:
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
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)
- The first line carries only the overall status (emoji + short conclusion).
- One block per task, separated by a blank line.
- The block's first line marks the task status with an emoji: ✅ completed / ❌ failed / 🚫 cancelled.
- For a single successful task, use 🔷 on the block's first line.
- Fixed field order:
token→target→amount, using full-width colon:and full-width spaceas separators when rendering in Chinese. - Each Tx on its own line, showing the full URL (do not use markdown link shorthand).
- Do not show
batchId, do not show elapsed time.
Information-query output
Which chains / networks / tokens are supported
Steps:
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-returnedchain.Network: the server-returnednetwork.Tokens: alltokens[].tokenunder 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.orNo 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[].tokenequals 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 ethereummainnetETH,USDT,USDCAvailable tronmainnetTRX,USDTUnavailable Testnet
Chain Network Tokens Availability ethereumsepoliaETH,USDTAvailable tronnileTRX,USDTAvailable
This scenario does not need pre-submit confirmation, because it creates no batch and triggers no on-chain transaction.
Self-check before output:
- Does it open with
Current BadWhale API support:(or its translation)? - Are there two sections, Mainnet and Testnet (with the fallback sentence even if a section is empty)?
- Is the table header exactly
Chain | Network | Tokens | Availability(exactly 4 columns)? - Are
available: falserows included (written asUnavailable), rather than only listing available items?
If any check fails → re-organize the output; do not send it to the user.