---
name: crypto-com-trading
description: "Trade and manage Crypto.com accounts across two surfaces: the Crypto.com App for buy/sell/swap, balances, fiat deposits and withdrawals, and App API error handling; and the Crypto.com Exchange for market data, spot orders, and advanced OCO/OTO/OTOCO orders. Use when the user asks to buy, sell, swap, check balances or portfolio, manage cash, deposit or withdraw fiat/crypto, place market/limit/stop orders, or call Crypto.com App or Exchange APIs."
metadata:
  author: "Crypto.com"
  source: "https://github.com/crypto-com/crypto-agent-trading"
  version: "1.0.1"
  license: "MIT"
---

# Crypto.com Trading Skill

> **Source repository:** https://github.com/crypto-com/crypto-agent-trading (MIT License)

Two independent trading surfaces — choose based on the user's intent:

| Surface | Use when | Auth | Method |
|---------|----------|------|--------|
| **Crypto.com App** | Buy/sell/swap tokens, fiat deposits/withdrawals, portfolio balances | `CDC_API_KEY` + `CDC_API_SECRET` | Node.js scripts (handles signing) |
| **Crypto.com Exchange** | Spot limit/market orders, advanced orders (OCO/OTO/OTOCO), market data | `EXCHANGE_API_KEY` + `EXCHANGE_SECRET_KEY` | Direct HMAC-signed HTTP (no scripts needed) |

### Quick Start (for any AI agent — Claude, OpenClaw, GPT, Gemini, etc.)

1. Determine which surface the user needs (see table above)
2. For **App**: clone repo, `npm install`, set env vars, run scripts via `npx tsx`
3. For **Exchange**: set env vars, use the Python signing function or equivalent to call the REST API directly — no repo clone needed

This skill is agent-agnostic. Any AI that can execute shell commands and read JSON responses can operate both surfaces.

---

## PART 1 — CRYPTO.COM APP

### Prerequisites

- Node.js 18+ and npm (for `npx tsx` and dependency install)
- git (to clone the scripts repo)
- A Crypto.com App account — [get your key](https://accounts.crypto.com/en/signup?from=skill-md)
- API key and secret — generate at App → Settings → API Keys ([how-to guide](https://help.crypto.com/en/articles/13843786-api-key-management))

### Setup

```bash
# 1. Clone the source repo
git clone https://github.com/crypto-com/crypto-agent-trading
cd crypto-agent-trading/crypto-com-app

# 2. Install dependencies
npm install

# 3. Set SKILL_DIR to the absolute path of the crypto-com-app directory
export SKILL_DIR="$(pwd)"

# 4. Set API credentials
export CDC_API_KEY="your-api-key"
export CDC_API_SECRET="your-api-secret"
```

All script paths below use `$SKILL_DIR`. Verify setup:
```bash
ls "$SKILL_DIR/scripts/account.ts"  # should exist
```

**Before any command, verify env vars are set:**
```bash
echo "CDC_API_KEY=${CDC_API_KEY:+set}" "CDC_API_SECRET=${CDC_API_SECRET:+set}"
```
If either shows empty, prompt the user to set them and stop.

### App — Key Rules

- **NEVER call the Crypto.com App API directly** (curl/fetch). Always use the TypeScript scripts — they handle HMAC signing, error formatting, and response filtering.
- All script output is JSON: `{"ok": true, "data": {...}}` or `{"ok": false, "error": "CODE", "error_message": "..."}`
- If `ok` is `false`, stop and report the error. Never proceed after a failed command.
- All trades require a **quote → confirm** two-step flow.

### App — Script Commands

#### Account

```bash
# Balances (scope: fiat | crypto | all)
npx tsx $SKILL_DIR/scripts/account.ts balances [fiat|crypto|all]

# Single token balance
npx tsx $SKILL_DIR/scripts/account.ts balance <SYMBOL>

# Weekly trading limit
npx tsx $SKILL_DIR/scripts/account.ts trading-limit

# Resolve funded source wallet for a trade type
npx tsx $SKILL_DIR/scripts/account.ts resolve-source <purchase|sale|exchange>

# Emergency kill switch — revoke API key
npx tsx $SKILL_DIR/scripts/account.ts revoke-key
```

#### Trading (quote → confirm flow)

```bash
# Step 1: Get quote (type: purchase | sale | exchange)
npx tsx $SKILL_DIR/scripts/trade.ts quote <type> '<json-params>'

# Step 2: Confirm order (use data.id from Step 1)
npx tsx $SKILL_DIR/scripts/trade.ts confirm <type> <quotation-id>

# View recent history
npx tsx $SKILL_DIR/scripts/trade.ts history
```

**Trade type mapping:**

| User says | Type | From | To |
|-----------|------|------|----|
| "Buy CRO with 100 USD" | `purchase` | USD (fiat) | CRO (crypto) |
| "Sell 0.1 BTC" | `sale` | BTC (crypto) | USD (fiat) |
| "Swap 0.1 BTC to ETH" | `exchange` | BTC (crypto) | ETH (crypto) |

**Quote JSON params:**

| Type | Example params |
|------|---------------|
| purchase | `{"from_currency":"USD","to_currency":"CRO","from_amount":"100"}` |
| sale | `{"from_currency":"BTC","to_currency":"USD","from_amount":"0.1","fixed_side":"from"}` |
| exchange | `{"from_currency":"BTC","to_currency":"ETH","from_amount":"0.1","side":"buy"}` |

**Full trading flow:**
1. Run quote command → read `data.id`, `data.from_amount`, `data.to_amount`, `data.countdown`
2. Ask user: `"Confirm: {from_amount} for {to_amount}? Valid for {countdown}s. Reply YES to proceed."`
3. If YES within countdown: run confirm command
4. If expired: `"Quote expired. Request a new quote."`

#### Coin Discovery

```bash
npx tsx $SKILL_DIR/scripts/coins.ts search '{"keyword":"BTC","sort_by":"rank","sort_direction":"asc","native_currency":"USD","page_size":10}'
```

#### Cash / Fiat

```bash
npx tsx $SKILL_DIR/scripts/fiat.ts discover                              # balances + networks
npx tsx $SKILL_DIR/scripts/fiat.ts payment-networks <CURRENCY>           # available networks
npx tsx $SKILL_DIR/scripts/fiat.ts deposit-methods <CURRENCY> <NETWORK>  # bank routing details
npx tsx $SKILL_DIR/scripts/fiat.ts email-deposit-info <CURRENCY> <TYPE>  # email instructions
npx tsx $SKILL_DIR/scripts/fiat.ts withdrawal-details <CURRENCY> <TYPE>  # quotas + fees
npx tsx $SKILL_DIR/scripts/fiat.ts create-withdrawal-order '<json>'      # create withdrawal
npx tsx $SKILL_DIR/scripts/fiat.ts create-withdrawal <ORDER_ID>          # execute withdrawal
npx tsx $SKILL_DIR/scripts/fiat.ts bank-accounts [CURRENCY]              # list linked banks
```

**Withdrawal JSON params:** `{"currency":"USD","amount":"500.00","viban_type":"us_ach","bank_account_id":"<optional>"}`

**Withdrawal flow:** `bank-accounts` → `withdrawal-details` → `create-withdrawal-order` → **confirm with user** → `create-withdrawal`
**Always confirm withdrawals** regardless of `confirmation_required` setting.

### App — Error Codes

| Error | Meaning | Response |
|-------|---------|---------|
| `MISSING_ENV` | API key/secret not set | Ask user to set env vars |
| `API_ERROR` | API rejected request | Report `error_message` |
| `QUOTATION_FAILED` | Quote rejected | Report `error_message` |
| `EXECUTION_FAILED` | Order confirmation failed | Report + suggest checking history |
| `RATE_LIMITED` | HTTP 429 | Wait 60s before retry |
| `invalid_quotation` | Quote expired/used | Request new quote |
| `not_enough_balance` | Insufficient funds | Check balances |
| `invalid_currency` | Bad currency code | Verify via coin search |
| `totp_required` | Withdrawal needs 2FA | Script prompts automatically |
| `withdrawal_limit_exceeded` | Withdrawal amount exceeds available quota or limits | Run `withdrawal-details`, report limits/fees, and ask for a lower amount or later retry |
| `invalid_bank_account` | Bank account is missing, unsupported, or not valid for the selected currency/network | Run `bank-accounts`, ask user to choose a valid linked account, or have them relink a bank in the App |
| `withdrawal_cooling_off` | Withdrawals are temporarily blocked by a security/account cooldown | Do not retry. Tell user to wait until the cooldown ends and check the App for the exact unlock time |
| `email_cooldown` | Deposit instruction email was requested too recently | Do not retry immediately. Tell user to wait for the cooldown window before requesting another email |

**Fiat-specific handling:** For withdrawal/cooldown errors, stop the flow before `create-withdrawal`. Never retry `withdrawal_cooling_off` or `email_cooldown` in a loop; surface the message to the user and wait for a new instruction.

### App — Rate Limits

- Max 10 trades/minute
- Max 100 API calls/minute
- On `RATE_LIMITED`: wait 60 seconds, inform user

### App — Kill Switch

Trigger on: "STOP ALL TRADING", "kill switch", or similar emergency.

1. **Always** prompt: `"⚠️ WARNING: This will immediately revoke your API key. Type 'CONFIRM KILL SWITCH' to proceed."`
2. Execute ONLY on exact phrase: `npx tsx $SKILL_DIR/scripts/account.ts revoke-key`
3. On success: `"🛑 Kill switch activated. Generate a new API key to resume."`

---

## PART 2 — CRYPTO.COM EXCHANGE

### Prerequisites

- Crypto.com Exchange account — [get your key](https://accounts.crypto.com/en/signup?from=exchange) then access the Exchange at https://crypto.com/exchange
- API keys: Exchange → Settings → API Keys → Create key (set permissions + IP whitelist)
- Python 3.7+ (or any language capable of HMAC-SHA256 + HTTP POST)
- No repo clone needed — this section is self-contained with inline signing code

### Setup

```bash
export EXCHANGE_API_KEY="your-exchange-api-key"
export EXCHANGE_SECRET_KEY="your-exchange-secret-key"
```

### Base URLs

| Environment | URL |
|-------------|-----|
| Production | `https://api.crypto.com/exchange/v1/` |
| UAT Sandbox | `https://uat-api.3ona.co/exchange/v1/` (institutional only) |

### Request Structure

All requests use a JSON envelope:

```json
{
  "id": 1,
  "method": "private/create-order",
  "api_key": "YOUR_API_KEY",
  "nonce": 1234567890123,
  "params": { ... },
  "sig": "HMAC_SHA256_HEX"
}
```

Public endpoints need only: `id`, `method`, `params`, `nonce`
Private endpoints additionally need: `api_key`, `sig`

**Headers required:**
```
Content-Type: application/json
```

### HMAC-SHA256 Signing (Private Endpoints)

**Step 1:** Sort `params` keys alphabetically, concatenate as `key+value` (no separators). For arrays, concatenate each element's sorted key-value pairs directly (no indices). For null values, use string `"null"`.

**Step 2:** Build payload: `{method}{id}{api_key}{param_string}{nonce}`

**Step 3:** Sign: `HMAC-SHA256(payload, secret_key)` → hex string

**Python signing implementation:**
```python
import hmac, hashlib, time, requests

def params_to_str(obj, level=0, MAX_LEVEL=3):
    if level >= MAX_LEVEL: return str(obj)
    if not isinstance(obj, dict): return str(obj)
    result = ""
    for key in sorted(obj):
        result += key
        if obj[key] is None: result += "null"
        elif isinstance(obj[key], list):
            for item in obj[key]: result += params_to_str(item, level+1)
        else: result += str(obj[key])
    return result

def sign_and_send(method, params, api_key, secret_key, base_url="https://api.crypto.com/exchange/v1"):
    req = {"id": 1, "method": method, "api_key": api_key,
           "params": params, "nonce": int(time.time() * 1000)}
    param_str = params_to_str(params)
    payload = method + str(req["id"]) + api_key + param_str + str(req["nonce"])
    req["sig"] = hmac.new(secret_key.encode(), payload.encode(), hashlib.sha256).hexdigest()
    resp = requests.post(f"{base_url}/{method}", json=req,
                         headers={"Content-Type": "application/json"})
    data = resp.json()
    if data.get("code") != 0:
        raise Exception(f"API error {data.get('code')}: {data.get('message', 'unknown')}")
    return data
```

### Exchange — Endpoint Quick Reference

#### Public (GET, no auth)

| Endpoint | Description | Required params |
|----------|-------------|-----------------|
| `public/get-instruments` | All tradeable instruments | None |
| `public/get-tickers` | Ticker prices | None (or `instrument_name`) |
| `public/get-book` | Order book | `instrument_name`, `depth` (≥1) |
| `public/get-candlestick` | OHLCV data | `instrument_name` |
| `public/get-trades` | Recent public trades | `instrument_name` |
| `public/get-valuations` | Index/mark prices | `instrument_name`, `valuation_type` |

#### Private Orders (POST, auth required)

| Endpoint | Description |
|----------|-------------|
| `private/create-order` | Place LIMIT or MARKET order |
| `private/amend-order` | Modify an active order |
| `private/cancel-order` | Cancel a single order |
| `private/cancel-all-orders` | Cancel all orders (optionally by instrument) |
| `private/get-open-orders` | List all active orders (no pagination — returns ALL) |
| `private/get-order-detail` | Get specific order by `order_id` or `client_oid` |
| `private/get-order-history` | Historical orders |
| `private/get-trades` | Account trade history |

#### Private Advanced Orders (POST, auth required)

| Endpoint | Description |
|----------|-------------|
| `private/advanced/create-order` | Trigger order: STOP_LOSS, STOP_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT |
| `private/advanced/create-oco` | One-Cancels-the-Other (2 legs: LIMIT + trigger) |
| `private/advanced/create-oto` | One-Triggers-the-Other (2 legs: LIMIT + trigger, opposite side) |
| `private/advanced/create-otoco` | One-Triggers-OCO (3 legs: LIMIT + stop + take-profit, opposite side) |
| `private/advanced/cancel-oco/oto/otoco` | Cancel by `list_id` |
| `private/advanced/cancel-order` | Cancel individual OTO/OTOCO leg |
| `private/advanced/get-open-orders` | List open advanced orders |
| `private/advanced/get-order-detail` | Get advanced order detail |
| `private/advanced/get-order-history` | Advanced order history |

#### Private Account (POST, auth required)

| Endpoint | Description |
|----------|-------------|
| `private/user-balance` | Current wallet balances |
| `private/get-positions` | Active positions |
| `private/get-fee-rate` | Trading fee structure |
| `private/get-accounts` | Master/sub-account info |
| `private/create-withdrawal` | Withdraw crypto |
| `private/get-deposit-address` | Get deposit address |
| `private/get-currency-networks` | Supported networks + fees |
| `private/get-deposit-history` | Deposit history |
| `private/get-withdrawal-history` | Withdrawal history |

### Exchange — Key Parameters

**Critical type rules (wrong type = API error):**
- **Must be strings:** `price`, `quantity`, `notional`, `ref_price`, `amount`, `new_price`, `new_quantity`
- **Must be numbers:** `limit`, `id`, `nonce`
- **Instrument name format:** `{BASE}_{QUOTE}` — case-sensitive, always uppercase (e.g., `BTC_USD`, `ETH_USDT`, `CRO_BTC`). Use `public/get-instruments` to discover valid pairs.
- Public = GET only, Private = POST only

**Order types:** `LIMIT` | `MARKET`
**Order sides:** `BUY` | `SELL`
**Advanced types:** `STOP_LOSS` | `STOP_LIMIT` | `TAKE_PROFIT` | `TAKE_PROFIT_LIMIT`
**Time-in-force:** `GOOD_TILL_CANCEL` | `IMMEDIATE_OR_CANCEL` | `FILL_OR_KILL`

### Exchange — Example: Place Limit Buy

```python
result = sign_and_send("private/create-order", {
    "instrument_name": "BTC_USD",
    "side": "BUY",
    "type": "LIMIT",
    "price": "93000",       # string!
    "quantity": "0.1"       # string!
}, EXCHANGE_API_KEY, EXCHANGE_SECRET_KEY)
```

**Always confirm with user before executing:** `"Confirm: LIMIT BUY 0.1 BTC_USD @ $93,000? Type CONFIRM to proceed."`

### Exchange — Advanced Orders

**OCO (One-Cancels-the-Other):** 2 legs — one LIMIT + one trigger. When one fills, the other cancels.
```python
sign_and_send("private/advanced/create-oco", {"order_list": [
    {"instrument_name":"BTC_USD","quantity":"0.1","type":"LIMIT","price":"108000","side":"SELL"},
    {"instrument_name":"BTC_USD","quantity":"0.1","type":"STOP_LOSS","ref_price":"80000","side":"SELL"}
]}, api_key, secret)
# Returns: {"list_id": "6498090546073120100"}
```

**OTO (One-Triggers-the-Other):** When LIMIT fills, triggers second order. Second leg must be opposite side.
```python
sign_and_send("private/advanced/create-oto", {"order_list": [
    {"instrument_name":"BTC_USD","quantity":"0.1","type":"LIMIT","price":"93000","side":"BUY"},
    {"instrument_name":"BTC_USD","quantity":"0.1","type":"STOP_LOSS","ref_price":"80000","side":"SELL"}
]}, api_key, secret)
```

**OTOCO:** Leg 1 = LIMIT, Leg 2 = take-profit trigger, Leg 3 = stop-loss trigger. All triggers opposite side.
```python
sign_and_send("private/advanced/create-otoco", {"order_list": [
    {"instrument_name":"BTC_USD","quantity":"0.1","type":"LIMIT","price":"93000","side":"BUY"},
    {"instrument_name":"BTC_USD","quantity":"0.1","type":"STOP_LOSS","ref_price":"80000","side":"SELL"},
    {"instrument_name":"BTC_USD","quantity":"0.1","type":"TAKE_PROFIT","ref_price":"108000","side":"SELL"}
]}, api_key, secret)
```

### Exchange — Rate Limits

| Endpoint | Limit |
|----------|-------|
| `create-order`, `cancel-order`, `cancel-all-orders` | 15 req / 100ms |
| `get-order-detail` | 30 req / 100ms |
| `get-trades`, `get-order-history` | 1 req / second |
| Other private endpoints | 3 req / 100ms |
| Public market data | 100 req / second (per IP) |

Max open orders: 200 per pair, 1,000 across all pairs.

### Exchange — Common Gotchas

- `amend-order` requires BOTH `new_price` AND `new_quantity` even if only one changed
- `create-order-list` (batch) max 10 orders; does NOT support OCO/OTO/OTOCO — use `advanced/*` endpoints
- `get-open-orders` has NO pagination — returns all open orders regardless of params
- Withdrawal addresses must be pre-whitelisted in Exchange settings
- Withdrawal `amount` is gross (includes fee); recipient receives `amount - fee`
- For multi-chain tokens (USDC, USDT etc.), always specify `network_id`
- Limit orders far from market price return error 315 (FAR_AWAY_LIMIT_PRICE)

---

## PART 3 — CHOOSING THE RIGHT SURFACE

| User intent | Surface to use |
|-------------|---------------|
| "Buy/sell/swap [token]" (casual, app-style) | App |
| "Check my balance / portfolio" | App |
| "Deposit or withdraw cash (USD/EUR etc.)" | App |
| "Place a limit order" / "market order" | Exchange |
| "Set a stop-loss" / "take profit" | Exchange (advanced orders) |
| "Set up an OCO / bracket order" | Exchange (advanced orders) |
| "Check order book / candlesticks" | Exchange (public endpoints) |
| Both surfaces | Run both — they are independent accounts |

---

## Security Notes

- API keys are read from env vars only — never store in files or logs
- App API keys cannot withdraw funds — trading only
- Exchange withdrawal requires address whitelisting
- All private requests use HMAC-SHA256 — never skip signing
- Kill switch (App): `revoke-key` command — requires explicit confirmation
- Exchange: require user to type "CONFIRM" before any production trade
- Weekly trading limit (App): configurable cap on total volume — check with `trading-limit`

---

## License

This skill and the source repository are licensed under the [MIT License](https://github.com/crypto-com/crypto-agent-trading/blob/main/LICENSE).

---

## Resources

- [Official Crypto.com website](https://crypto.com)
- [Crypto.com AI Trading](https://crypto.com/en/ai-trading)
- [Getting Started](https://help.crypto.com/en/articles/13843782-getting-started)
- [API Key Management](https://help.crypto.com/en/articles/13843786-api-key-management)
- [Weekly Trading Limit](https://help.crypto.com/en/articles/13843797-weekly-trading-limit)
- [Safety and Security](https://help.crypto.com/en/articles/13843804-safety-and-security)
- [OpenClaw Trading Overview](https://help.crypto.com/en/articles/13843765-openclaw-trading-overview)
- [Crypto AI Trading Bots: A Beginner's Guide](https://crypto.com/en/crypto/learn/crypto-ai-trading-bots-a-beginners-guide)
- [How to Automate Trading on the Crypto.com Exchange](https://crypto.com/en/crypto/learn/how-to-automate-trading-on-crypto-com)
- [Source repo](https://github.com/crypto-com/crypto-agent-trading)
