
Crypto Com App
- 860 installs
- 16 repo stars
- Updated June 10, 2026
- crypto-com/crypto-agent-trading
crypto-com-app is a Claude Code skill that executes cryptocurrency trades, fiat deposits and withdrawals, and balance queries through the Crypto.com APP API for developers who want agent-driven trading and wallet managem
About
crypto-com-app is an Apache-2.0 agent skill at version 1.0.2 from crypto-com/crypto-agent-trading that connects coding agents to the Crypto.com APP API. It supports buy, sell, swap, and exchange operations plus cash deposits, withdrawals, bank account details, weekly trading limits, portfolio positions, market prices, and transaction history. The skill covers BTC, ETH, CRO, and 200+ tokens across fiat and crypto wallets. Developers reach for crypto-com-app when building agent workflows that quote, confirm, and execute trades or manage fiat wallet operations without hand-writing every APP API call.
- Buy, sell, swap, and exchange across 200+ tokens with fiat and crypto wallets
- Cash deposit and withdrawal flows with bank accounts, limits, and payment networks
- Balances, market prices, portfolio positions, and transaction history queries
- Quote → confirm multi-turn flow with optional confirmation_required preference
- Runs via npx tsx scripts using CDC_API_KEY and CDC_API_SECRET
Crypto Com App by the numbers
- 860 all-time installs (skills.sh)
- +25 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #175 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/crypto-com/crypto-agent-trading --skill crypto-com-appAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 860 |
|---|---|
| repo stars | ★ 16 |
| Security audit | 1 / 3 scanners passed |
| Last updated | June 10, 2026 |
| Repository | crypto-com/crypto-agent-trading ↗ |
How do you trade crypto from a coding agent?
Wire your coding agent to Crypto.com so you can quote, confirm, and run buy/sell/swap trades plus fiat deposits and withdrawals from chat.
Who is it for?
Developers building agent workflows that need programmatic Crypto.com trading, wallet management, and fiat operations with user confirmation in chat.
Skip if: Developers who only need market data without trading, lack Crypto.com APP API credentials, or cannot grant agents network and secrets access.
When should I use this skill?
The user asks to buy, sell, swap crypto, deposit or withdraw cash, check Crypto.com balances, view trading limits, or manage fiat wallet operations from an agent.
What you get
Confirmed trades, swap or exchange orders, fiat deposit or withdrawal instructions, and queried balances, limits, portfolio positions, and transaction history.
- Executed trade or wallet operation
- Queried balance and transaction report
By the numbers
- Skill version 1.0.2 under Apache-2.0 license
- Supports BTC, ETH, CRO, and 200+ tokens
Files
Skill: crypto-com-app
Agent Capability Requirements
This skill requires your agent platform to support the following capabilities. If your platform lacks any required capability, the skill will not function.
| Capability | Required | Details |
|---|---|---|
| Shell command execution | Yes | Must be able to run npx tsx ./scripts/... and capture stdout |
| Environment variables | Yes | Must read CDC_API_KEY and CDC_API_SECRET from the shell environment |
| JSON parsing | Yes | Must parse structured JSON from script stdout to extract fields |
| Multi-turn conversation | Yes | Trading uses a quote → confirm flow that spans multiple user turns |
| Persistent memory | No | Used for confirmation_required preference. If unsupported, default to always confirming trades |
| Elapsed-time awareness | No | Used to check quote expiry (countdown field). If unsupported, always attempt confirmation and handle invalid_quotation errors gracefully |
CRITICAL: How This Skill Works
You MUST use the TypeScript scripts for ALL API interactions. NEVER call the API directly with `curl`, `fetch`, or any other HTTP method.
The scripts handle request signing, error handling, and response formatting. If you bypass them:
- The request will fail (missing HMAC signature)
- The response won't be filtered or structured
For every user request, find the matching command below and run it via `npx tsx`. Read the JSON output. Act on it.
Configurations
- BASE_URL:
https://wapi.crypto.com - CDC_API_KEY:
{{env.CDC_API_KEY}} - CDC_API_SECRET:
{{env.CDC_API_SECRET}} - CONFIRMATION_REQUIRED:
{{memory.confirmation_required}}(Default: true) - SKILL_DIR: The directory containing this
SKILL.mdfile. Resolve it from the path you loaded this file from (e.g. if you read/home/user/skills/crypto-com-app/SKILL.md, thenSKILL_DIRis/home/user/skills/crypto-com-app).
Environment Setup
- Both
CDC_API_KEYandCDC_API_SECRETmust be set as environment variables before use. - Before running any script, check whether both variables are set by running:
echo "CDC_API_KEY=${CDC_API_KEY:+set}" "CDC_API_SECRET=${CDC_API_SECRET:+set}"If either prints empty instead of set, prompt the user:
"Your API credentials are not configured. Please set them in your terminal before I can proceed:
```
export CDC_API_KEY="your-api-key"
export CDC_API_SECRET="your-api-secret"
```
You can generate an API key at https://help.crypto.com/en/articles/13843786-api-key-management.
Let me know once you've set them."
Then stop and wait for the user to confirm before retrying.
- If a script returns a
MISSING_ENVerror, treat it the same way: prompt the user to set the variables and wait.
Script Commands
ALL API interactions MUST go through these scripts. They handle signing, execution, filtering, and error formatting. Run the appropriate command below via shell, then parse the JSON output.
Prerequisite: npx tsx (Node.js 18+ required; tsx is fetched automatically by npx).
Important: All script paths below use $SKILL_DIR as a placeholder for this skill's root directory. Resolve it from the path you loaded this SKILL.md from, or cd into the skill directory and use ./scripts/... as the path. Either approach works.
Account Commands
# Filtered non-zero balances (scope: fiat | crypto | all)
npx tsx $SKILL_DIR/scripts/account.ts balances [fiat|crypto|all]
# Single token balance lookup
npx tsx $SKILL_DIR/scripts/account.ts balance <SYMBOL>
# Weekly trading limit
npx tsx $SKILL_DIR/scripts/account.ts trading-limit
# Find funded source wallets for a trade type
npx tsx $SKILL_DIR/scripts/account.ts resolve-source <purchase|sale|exchange>
# Kill switch — revoke API key
npx tsx $SKILL_DIR/scripts/account.ts revoke-keyTrade Commands
Trading follows a two-step flow: get a quotation first, then confirm the order.
# Step 1 — Get quotation (type: purchase | sale | exchange)
npx tsx $SKILL_DIR/scripts/trade.ts quote <type> '<json-params>'
# Returns: {"ok": true, "data": {"id": "<quotation-id>", "from_amount": {...}, "to_amount": {...}, "countdown": 15, ...}}
# Step 2 — Confirm order: pass the data.id from Step 1 as <quotation-id>
npx tsx $SKILL_DIR/scripts/trade.ts confirm <type> <quotation-id>
# View recent transactions
npx tsx $SKILL_DIR/scripts/trade.ts historyHow to map user intent to trade type:
| User says | Trade 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) |
Quotation JSON params by trade type:
| Type | JSON fields |
|---|---|
| purchase | {"from_currency":"USD","to_currency":"CRO","from_amount":"100"} or use to_amount instead |
| 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"} |
Example — "Buy CRO with 100 USD":
1. Run: npx tsx $SKILL_DIR/scripts/trade.ts quote purchase '{"from_currency":"USD","to_currency":"CRO","from_amount":"100"}' 2. Read data.id, data.from_amount, data.to_amount, data.countdown from the response. 3. If confirmation required (default): Ask user "Confirm: 100 USD for X CRO? Valid for {countdown}s. Reply 'YES' to proceed."
- If user says YES (within countdown):
npx tsx $SKILL_DIR/scripts/trade.ts confirm purchase <data.id>
4. If confirmation opted out (memory.confirmation_required is false): Skip asking and immediately run npx tsx $SKILL_DIR/scripts/trade.ts confirm purchase <data.id>
Opt-in / Opt-out: Users can say "stop asking for confirmation" to auto-execute trades, or "require confirmation" to re-enable the prompt. See Section 3 below.
Coin Discovery Commands
# Search coins
npx tsx $SKILL_DIR/scripts/coins.ts search '{"keyword":"BTC","sort_by":"rank","sort_direction":"asc","native_currency":"USD","page_size":10}'Required JSON parameters:
| Parameter | Type | Allowed values |
|---|---|---|
sort_by | string | rank, market_cap, alphabetical, volume, performance |
sort_direction | string | asc, desc |
native_currency | string | Uppercase currency code (e.g. USD) |
keyword | string | Search string, 1–100 chars; matches coin name and symbol only |
page_size | integer | Number of results per page |
Optional: page_token — opaque token for fetching the next page (see pagination below).
Pagination: The response includes a pagination object with has_more (boolean) and next_page_token (string). When has_more is true, pass next_page_token as page_token in the next request to fetch the next page.
Key response fields per coin: rails_id (identical to currency_id / currency in trade and account APIs — use this to cross-reference), price_native, price_usd, percent_change_*_native (price performance over past timeframes, e.g. percent_change_24h_native).
Cash (Fiat) Commands
Cash commands handle deposits, withdrawals, and bank account management.
# Cash overview — balances + available payment networks per currency
npx tsx $SKILL_DIR/scripts/fiat.ts discover
# Payment networks for a currency
npx tsx $SKILL_DIR/scripts/fiat.ts payment-networks <CURRENCY>
# Deposit method details (bank routing info)
npx tsx $SKILL_DIR/scripts/fiat.ts deposit-methods <CURRENCY> <DEPOSIT_METHOD>
# Email deposit instructions to user
npx tsx $SKILL_DIR/scripts/fiat.ts email-deposit-info <CURRENCY> <VIBAN_TYPE>
# Withdrawal details (quotas, fees, minimums)
npx tsx $SKILL_DIR/scripts/fiat.ts withdrawal-details <CURRENCY> <VIBAN_TYPE>
# Create withdrawal order (returns order with fees + receivable amount)
npx tsx $SKILL_DIR/scripts/fiat.ts create-withdrawal-order '<json-params>'
# Execute withdrawal (may prompt for TOTP authenticator code)
npx tsx $SKILL_DIR/scripts/fiat.ts create-withdrawal <ORDER_ID>
# List linked bank accounts
npx tsx $SKILL_DIR/scripts/fiat.ts bank-accounts [CURRENCY]Key parameters:
| Parameter | Description | Example values |
|---|---|---|
CURRENCY | Uppercase currency code | USD, EUR, GBP, AUD |
DEPOSIT_METHOD | Network ID from payment-networks | us_ach, sepa, uk_fps |
VIBAN_TYPE | Same as deposit method / withdrawal network | us_ach, sepa, uk_fps |
Withdrawal order JSON params:
| Field | Required | Description |
|---|---|---|
currency | Yes | Currency code (e.g. "USD") |
amount | Yes | Amount as string (e.g. "500.00") |
viban_type | Yes | Payment network (e.g. "us_ach") |
bank_account_id | No | Specific bank account ID |
Example — "How do I deposit USD?":
1. Run: npx tsx $SKILL_DIR/scripts/fiat.ts payment-networks USD 2. Read data array — each entry has deposit_push_payment_networks (e.g. ["us_ach", "us_wire_transfer"]) 3. For details: npx tsx $SKILL_DIR/scripts/fiat.ts deposit-methods USD us_ach 4. Read data array — contains bank_details with routing number, account number, etc.
Example — "Withdraw 500 USD via ACH":
1. Run: npx tsx $SKILL_DIR/scripts/fiat.ts withdrawal-details USD us_ach — check quotas and fees 2. Run: npx tsx $SKILL_DIR/scripts/fiat.ts create-withdrawal-order '{"currency":"USD","amount":"500","viban_type":"us_ach"}' 3. Read data.id (order ID), data.fee, data.receivable_amount from response 4. Confirm with user: "Withdraw 500 USD via ACH. Fee: {fee}. You'll receive: {receivable_amount}. Confirm?" 5. If YES: npx tsx $SKILL_DIR/scripts/fiat.ts create-withdrawal <order-id> 6. If TOTP required, the script will prompt for a 6-digit authenticator code on stderr
Output Format
Every script prints structured JSON to stdout:
Success:
{"ok": true, "data": { ... }}Error:
{"ok": false, "error": "ERROR_CODE", "error_message": "Human-readable message"}Constraints
- Validation: Success requires
ok: truein the script output. - Confirmation Window: Quote validity is defined by the
countdownfield in the quotation data. - Execution Warning: If order confirmation takes > 5s, notify: "Order submitted but taking longer than expected. Check order status with 'Show recent trades'".
- Rate Limits:
- Max 10 trades per minute.
- Max 100 API calls per minute.
- On HTTP 429 (
RATE_LIMITEDerror): wait 60 seconds before retrying the same request. Inform the user: "Rate limit reached — please wait 60 seconds before trying again."
Error Handling
All scripts return structured errors. Parse the error field to determine the appropriate response.
Script Error Codes
These are the error values in the script's JSON output. They tell you what category of failure occurred.
| Error Code | Meaning | Agent Response |
|---|---|---|
MISSING_ENV | CDC_API_KEY or CDC_API_SECRET not set | Tell user to set env vars via terminal |
API_ERROR | API returned non-200 or ok !== true | Report: "Transaction failed: {error_message}" |
INVALID_ARGS | Bad command-line arguments | Show correct usage from the error_message |
QUOTATION_FAILED | Quotation request rejected by API | Report the error_message to user (see API errors below) |
EXECUTION_FAILED | Order confirmation failed | Report and suggest: "Check order status with 'Show recent trades'" |
API_KEY_NOT_FOUND | Key already revoked or does not exist | "API key not found — it may have already been revoked." |
RATE_LIMITED | Too many requests (HTTP 429) | "Rate limit reached — please wait 60 seconds before trying again." |
UNKNOWN | Unexpected error | Report the raw error_message |
Rule: When ok is false in the output, stop the current operation and report the error to the user using the guidance above. Never proceed to the next step after a failure.
Common API Errors (Quick Reference)
These are the specific API error codes that appear inside the error_message of QUOTATION_FAILED, EXECUTION_FAILED, or API_ERROR responses. They tell you why the API rejected the request.
error | Meaning | Recovery |
|---|---|---|
not_enough_balance | Insufficient funds | Check balances, reduce trade amount |
invalid_currency | Currency code not recognized | Verify via coin search |
invalid_quotation | Quote expired or already used | Request a new quotation |
failed_to_create_quotation | Quotation engine error | Retry shortly |
not_eligible_for_prime | Not eligible for Prime benefits | Proceed without Prime |
unauthorized | Account not approved for trading | Contact support |
restricted_feature | Feature restricted on account | Report error_message to user |
existing_currency_order_error | An existing order is in progress | Wait or cancel existing order |
viban_purchase_not_enabled | Fiat-to-crypto not enabled | Account feature not available |
crypto_viban_not_enabled | Crypto-to-fiat not enabled | Account feature not available |
bank_transfer_not_enabled | Bank transfer not enabled | Account feature not available |
missing_parameter | Required parameter missing | Script bug — report it |
failed_to_create_transaction | Transaction creation failed | Retry or contact support |
key_not_active | API key revoked or expired | Generate a new API key, update env vars |
api_key_not_found | Key doesn't exist or belongs to another user | Verify correct key is set in CDC_API_KEY |
totp_required | Withdrawal needs 2FA code | Script handles automatically — prompts user for authenticator code |
withdrawal_limit_exceeded | Daily/monthly quota exceeded | Show limits via withdrawal-details, reduce amount |
invalid_bank_account | Bank account not eligible | Check bank-accounts for valid accounts with status: completed |
withdrawal_cooling_off | Recently changed withdrawal settings | Wait for cooling-off period, report error_message to user |
email_cooldown | Too many deposit info emails | Wait for cooldown period (shown in error), try again later |
For dynamic errors (limit exceeded, currency disabled, cooling-off, etc.), report the error and error_message directly to the user. For full details, see references/errors.md.
---
Logic & Rules
1. Asset & Source Disambiguation
Determine the trade type first:
- Purchase — fiat → crypto
- Sale — crypto → fiat
- Exchange — crypto → crypto
Then resolve the source wallet:
- For purchase: run
npx tsx $SKILL_DIR/scripts/account.ts resolve-source purchase. The script returns only funded fiat entries. - For sale or exchange: run
npx tsx $SKILL_DIR/scripts/account.ts resolve-source sale(orexchange). The script returns only funded crypto entries.
Result (from `data.status`):
- `SELECTED` → auto-select
data.currency. - `AMBIGUOUS` → prompt user to choose from
data.options. - `EMPTY` → inform user "No funded wallets found" and stop.
"Sell All" Scenario: If user says "Sell all [TOKEN]", run npx tsx $SKILL_DIR/scripts/account.ts balance [TOKEN]. Use the data.available amount (or data.balance) as from_amount for the quotation.
2. Trading Process (Quotation → Confirmation → Execution)
When the user asks to buy, sell, or swap crypto, always follow this three-step flow:
- Step A — Get Quotation: Build the JSON params from the user's request (see the "Quotation JSON params" table in Trade Commands) and run:
npx tsx $SKILL_DIR/scripts/trade.ts quote <type> '<json-params>' Read data.id, data.from_amount, data.to_amount, and data.countdown from the response.
- Step B — Ask User to Confirm:
- IF
memory.confirmation_requiredistrue(or unset): - Prompt: "Confirm: {from_amount} for {to_amount}? Valid for {countdown}s. Reply 'YES' to proceed."
- Expiration Logic: If the user replies "YES" after
countdownseconds have elapsed, reject: "Transaction rejected: The quotation rate has expired. Please request a new quote." - Execute Step C ONLY if user replies "YES" within the valid window.
- ELSE (Opted Out):
- Notify: "Quotation received. Proceeding to execution automatically..."
- Immediately proceed to Step C.
- Step C — Execute Order: Run:
npx tsx $SKILL_DIR/scripts/trade.ts confirm <type> <data.id>using theidfrom Step A.
3. Memory Management (Opt-in/Out)
- To Opt-out: If user says "stop asking for confirmation" or "enable auto-trade", update
memory.confirmation_requiredtofalse. - To Opt-in: If user says "require confirmation" or "enable manual trade", update
memory.confirmation_requiredtotrue. - Platforms without persistent memory: If your platform does not support
{{memory.*}}, treatconfirmation_requiredas alwaystrue(safest default).
4. Error Handling
- All script outputs include an
okfield. Success is defined ONLY asok: true. - If
okisfalse, readerrorand respond per the Error Handling table above. - Never proceed to the next step after a failed command.
5. Account & History
- History: Run
npx tsx $SKILL_DIR/scripts/trade.ts history— display the entries fromdata. - Weekly Trading Limit: Run
npx tsx $SKILL_DIR/scripts/account.ts trading-limit— display as: "📊 Weekly Trading Limit: {data.used} / {data.limit} USD (Remaining: {data.remaining} USD)". - Fiat vs Crypto balance routing: When the user asks about a specific currency balance, determine whether it is a fiat currency (USD, EUR, GBP, AUD, SGD, CAD, BRL, etc.) or a crypto token (BTC, ETH, CRO, etc.).
- Fiat currency → run
npx tsx $SKILL_DIR/scripts/account.ts balances fiat(shows all fiat balances) ornpx tsx $SKILL_DIR/scripts/fiat.ts discover(shows balances + payment networks). Never usebalance <SYMBOL>for fiat currencies — it queries the crypto wallet and will always return 0. - Crypto token → run
npx tsx $SKILL_DIR/scripts/account.ts balance <SYMBOL>. - Unsure → run
npx tsx $SKILL_DIR/scripts/account.ts balances allto show both fiat and crypto. - Balances (Categorized):
- If "List Fiat": run
npx tsx $SKILL_DIR/scripts/account.ts balances fiat. - If "List Crypto": run
npx tsx $SKILL_DIR/scripts/account.ts balances crypto. - If "List All": run
npx tsx $SKILL_DIR/scripts/account.ts balances all. Crucial: Display Fiat category first, followed by Crypto balances below. - The scripts automatically filter out zero-balance entries. If a category has no entries in the output, display "No holdings" under that header.
- Crypto balances (
data.crypto) contain anotefield ("available for trading") and awalletsarray. Always clarify to the user that these amounts are what's available for trading — total holdings across all products may be higher. - Portfolio Allocation: When crypto balances are queried, the output may include a
portfolio_allocationarray — each entry has a productnameandprice_native(USD value). Display this as a summary of the user's asset distribution across products (e.g. Crypto Wallet, Exchange, Earn, Staking, etc.). - Single token balance (
balance <SYMBOL>) output may include aproduct_allocationobject — keys are product names (e.g.crypto_earn,staking,supercharger,crypto_basket,airdrop_arena) and values are the token amounts held in each. Only non-zero products are included. Summarize these allocations to the user alongside the available-for-trading amount so they see the full picture of where their tokens are held.
6. Kill Switch
- Trigger: User says "STOP ALL TRADING", "kill switch", or similar emergency stop command.
- ALWAYS require explicit confirmation regardless of
memory.confirmation_required: - Prompt: "⚠️ WARNING: This will immediately revoke your API key and disable all trading. A new API key must be generated to resume. Type 'CONFIRM KILL SWITCH' to proceed."
- Execute ONLY if user replies with the exact phrase.
- Execution: Run
npx tsx $SKILL_DIR/scripts/account.ts revoke-key. - On success (`ok: true`): Notify: "🛑 Kill switch activated. API key has been revoked. All trading is disabled. Generate a new API key and update your environment variables to resume."
- On `API_KEY_NOT_FOUND` error: Notify: "API key not found — it may have already been revoked or does not exist."
- Idempotency: Revoking an already-revoked key is not an error; treat it the same as a successful revocation.
7. Balance Display Format
- Fiat Header: "🏦 Fiat Balances"
- Crypto Header: "🪙 Crypto Balances"
- Always list Fiat section before Crypto section when both are requested.
- Never display zero-balance assets. Only show assets with a balance greater than 0. If all assets in a category are zero, show "No holdings" under that header.
8. Cash Deposit & Withdrawal
Terminology: Use "cash" (not "fiat") in user-facing messages. Say "your cash balance" not "your fiat balance".
Currency disambiguation: If the user doesn't specify a currency, run discover first.
- One currency with balance → auto-select it and proceed.
- Multiple currencies with balances → present the list and ask the user to choose before proceeding.
- No balances → inform: "You don't have any cash balances yet." and stop.
Deposit flow: 1. Run discover to show the user their cash currencies and available networks 2. User picks a currency and network — run deposit-methods to get bank details 3. Present the bank details (routing number, account number, bank name, reference) 4. Optionally run email-deposit-info to email instructions to the user 5. Rate limit: email-deposit-info is limited to 5 requests per 30 minutes. On cooldown error, inform user and show the cooldown_in_seconds value.
Withdrawal flow: 1. Run bank-accounts <currency> to list eligible accounts (filter to status: "completed" only)
- One eligible account → auto-select it
- Multiple eligible accounts → present the list (bank name, account identifier, networks) and ask user to choose
- No eligible accounts → inform: "No eligible bank accounts found for {currency}. You need to link a bank account first." and stop
2. Run withdrawal-details to check quotas, fees, and minimums 3. Run create-withdrawal-order with amount, network, and bank_account_id from step 1 — returns order with fees 4. ALWAYS confirm with user before executing (regardless of memory.confirmation_required):
- Show: amount, fee, receivable amount, network, destination bank account, processing time
- Require explicit "YES" to proceed
5. Run create-withdrawal with the order ID 6. TOTP handling: If the API returns totp_required, the script prompts for a 6-digit authenticator code on stderr. The agent should tell the user: "Please enter your 6-digit authenticator code when prompted." Never try to generate or bypass the TOTP.
Bank accounts:
- Run
bank-accountsto list linked accounts. Filter by currency if specified. - Only accounts with
status: "completed"are eligible for withdrawals. - Each account shows
withdrawal_payment_networks— use these as validviban_typevalues.
Deposit methods — vendor selection:
deposit-methodsreturns avendor_listarray. Each vendor has astatusfield.- Only show vendors with
status: "created"(active). Ignore"uncreated"vendors. - If multiple created vendors exist, default to the first one in the list.
Cash commands quick reference:
| User says | Commands to run |
|---|---|
| "How do I deposit USD?" | payment-networks USD then deposit-methods USD <network> |
| "Email me deposit instructions" | email-deposit-info <currency> <viban_type> |
| "Show my bank accounts" | bank-accounts |
| "What are my withdrawal limits?" | withdrawal-details <currency> <viban_type> |
| "Withdraw 500 USD" | withdrawal-details -> create-withdrawal-order -> confirm -> create-withdrawal |
| "What currencies can I deposit?" | discover |
{
"slug": "crypto-com-app",
"version": "1.0.2",
"publishedAt": 1774857600
}
Error Reference
This document covers error scenarios, API error codes, and recovery guidance. The scripts return structured errors — use this reference when the compact table in SKILL.md needs more context.
Error format:
{"ok": false, "error": "error_code", "error_message": "Human-readable message"}---
Script Error Codes
These are the error values returned by the TypeScript scripts when a problem is caught before or after the API call.
MISSING_ENV
Cause: CDC_API_KEY or CDC_API_SECRET environment variable is not set.
Recovery: Tell the user to set both variables in their terminal:
export CDC_API_KEY="your-api-key"
export CDC_API_SECRET="your-api-secret"API_ERROR
Cause: The API returned a non-200 HTTP status or the response body has ok: false.
Recovery: Report the error_message to the user. If the message is vague, suggest checking API key validity and account status.
INVALID_ARGS
Cause: The script received bad or missing command-line arguments (wrong scope, missing symbol, malformed JSON, unknown trade type).
Recovery: The error_message includes correct usage. Show it to the user or fix the command and retry.
QUOTATION_FAILED
Cause: The quotation request was rejected by the API. See the API error tables below for specific codes.
Recovery: Report the error_message. Suggest the user adjust trade parameters and try again.
EXECUTION_FAILED
Cause: The order confirmation failed after a successful quotation. See the API error tables below for specific codes.
Recovery: Report the error. Suggest: "Check order status with 'Show recent trades' to verify whether the order was partially processed."
API_KEY_NOT_FOUND
Cause: The API key does not exist or has already been revoked.
Recovery: Inform the user: "API key not found — it may have already been revoked or does not exist." The key is already inactive.
UNKNOWN
Cause: An unexpected error (network failure, JSON parse error, script crash).
Recovery: Report the raw error_message. If it's a network issue, suggest checking connectivity.
---
API Error Codes — Quotation
These errors can appear when requesting a quotation via trade.ts quote. They apply across purchase, sale, and exchange operations unless noted otherwise.
Balance & Eligibility
error | error_message | Applies to | Recovery |
|---|---|---|---|
not_enough_balance | (none) | All | Insufficient funds. Check balances and reduce trade amount. |
unauthorized | (none) | All | User account not approved for trading. Contact support. |
restricted_feature | varies | All | Account has a feature restriction. The error_message explains which feature. |
not_eligible_for_prime | "user is not eligible for prime" | All | User is not eligible for Prime benefits. Proceed without Prime. |
Currency & Pair
error | error_message | Applies to | Recovery |
|---|---|---|---|
invalid_currency | (none) | Exchange | Currency code not recognized. Verify spelling via coin search. |
existing_currency_order_error | varies (from QRE) | Exchange | An existing order for this currency pair is in progress. Wait or cancel it first. |
failed_to_create_quotation | "failed to create quotation" | All | Internal quotation engine error. Retry in a moment. |
Feature Availability
error | error_message | Applies to | Recovery |
|---|---|---|---|
viban_purchase_not_enabled | (none) | Purchase | Fiat-to-crypto purchase is not enabled for this account. |
bank_transfer_not_enabled | (none) | Purchase, Sale | Bank transfer feature is not enabled. |
crypto_viban_not_enabled | (none) | Sale | Crypto-to-fiat sale is not enabled for this account. |
Authentication
error | error_message | Applies to | Recovery |
|---|---|---|---|
invalid_passcode | (none) | All | Passcode verification failed. |
passcode_not_set | (none) | All | User has not set a passcode. HTTP 400. |
---
API Error Codes — Order Confirmation
These errors can appear when confirming an order via trade.ts confirm. They apply across purchase, sale, and exchange operations unless noted otherwise.
Quotation Issues
error | error_message | Applies to | Recovery |
|---|---|---|---|
invalid_quotation | (none) | All | Quotation has expired or was already used. Request a new quote. |
missing_parameter | (none) | All | quotation_id was not provided. This is a script bug — report it. |
Balance & Eligibility
error | error_message | Applies to | Recovery |
|---|---|---|---|
not_enough_balance | (none) | Exchange | Balance changed between quote and confirm. Check balances and re-quote. |
unauthorized | (none) | Exchange | User account not approved. Contact support. |
Transaction Failures
error | error_message | Applies to | Recovery |
|---|---|---|---|
failed_to_create_transaction | (none) | Sale | Internal error creating the transaction. Retry or contact support. |
failed_to_activate_prime_account | "failed to activate prime account" | All | Prime account activation failed during order. Retry without Prime. |
Feature Availability
error | error_message | Applies to | Recovery |
|---|---|---|---|
bank_transfer_not_enabled | (none) | Purchase | Bank transfer feature is not enabled for this account. |
restricted_feature | varies | Exchange | Account has a feature restriction. The error_message explains which feature. |
---
API Error Codes — API Keys
These errors can appear when interacting with API key endpoints (trading-limit, revoke-key). The /v1/api-keys path may return errors in either {"ok": false, "error": "..."} or {"code": "...", "message": "..."} format — the scripts handle both.
error / code | error_message / message | Meaning | Recovery |
|---|---|---|---|
key_not_active | (none) | API key has been revoked or expired | Generate a new API key and update env vars |
api_key_not_found | "not found" | Key doesn't exist or belongs to another user | Verify the correct API key is set in CDC_API_KEY |
invalid_scope | (none) | One or more scopes in the request don't exist | Verify the API key has the required scopes |
invalid_expiration | (none) | expires_at is in the past or more than 1 year out | Use a valid expiration date |
invalid_public_key | (none) | Not a valid PEM-encoded Ed25519 public key | Provide a valid Ed25519 public key |
passcode_required | (none) | Sensitive fields changed but no passcode provided | N/A for API-key-based trading |
Note: The key_not_active and api_key_not_found errors are the most relevant for this skill. If either appears during trading-limit or any other operation, the API key is no longer valid — the user must generate a new one.
---
Dynamic Error Codes
Some API errors return dynamic codes and messages from internal subsystems. These will not match the exact strings above but follow the same {"ok": false, "error": "...", "error_message": "..."} format. Common categories:
| Pattern | Meaning |
|---|---|
| Limit / exceeded errors | Trade amount exceeds a limit (weekly, per-trade, or per-currency). Reduce amount or check trading-limit. |
| Currency disabled errors | The specific currency is temporarily disabled for trading. Try a different currency or wait. |
| Cooling-off / trade restriction | Account is in a cooling-off period after a security event. Wait for the period to end. |
| Finalized / cancelling errors | The order is already finalized or being cancelled. No action needed. |
| Risk / payment rejected | The transaction was rejected by risk assessment. Contact support if recurring. |
| Account inactive / not accessible | The target account is inactive. Verify account status. |
| Unsupported strategy / payout | The requested trade strategy or payout method is not supported. Use a different method. |
When the agent encounters a dynamic error code not listed above, report the error and error_message directly to the user.
---
API HTTP Status Codes
The scripts handle these internally, but for reference:
| Status | Meaning |
|---|---|
| 200 | Success (must also check ok: true in body) |
| 400 | Bad request — invalid parameters |
| 401 | Unauthorized — invalid or missing API key |
| 403 | Forbidden — API key lacks required permissions |
| 404 | Endpoint not found |
| 429 | Rate limited — too many requests |
| 500 | Internal server error |
| 503 | Service unavailable — maintenance |
---
Troubleshooting Checklist
If a command fails unexpectedly:
1. Check env vars — echo $CDC_API_KEY should return a value (not empty) 2. Check API key status — run npx tsx ./scripts/account.ts trading-limit (from the skill directory). If it returns successfully, the key is valid. 3. Check network — can the machine reach https://wapi.crypto.com? 4. Check Node version — node --version should be 18+ 5. Check timestamps — system clock must be accurate for HMAC signing to work
import { apiGet, apiPost, assertOk } from "./lib/api.js";
import { ErrorCode, fail, run, success } from "./lib/output.js";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function filterFiat(balances: any[]): any[] {
return balances.filter((b: any) => parseFloat(b.amount?.amount ?? "0") > 0);
}
function filterCrypto(wallets: any[]): any[] {
return wallets.filter((w: any) => {
const amt = w.available?.amount ?? w.balance?.amount ?? "0";
return parseFloat(amt) > 0;
});
}
function parsePortfolioProducts(res: any): any[] | null {
if (res.data?.ok !== true) return null;
return (res.data.products as any[] ?? []).filter(
(p: any) => parseFloat(p.price_native?.amount ?? "0") > 0,
);
}
function parseCurrencyAllocation(res: any): Record<string, string> | null {
if (res.data?.ok !== true) return null;
const allocation: Record<string, string> = {};
for (const [key, val] of Object.entries(res.data)) {
if (key === "ok") continue;
const entry = val as any;
if (entry?.amount && parseFloat(entry.amount) > 0) {
allocation[key] = entry.amount;
}
}
return Object.keys(allocation).length > 0 ? allocation : null;
}
// ---------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------
async function balances(scope: string) {
const validScopes = ["fiat", "crypto", "all"];
if (!validScopes.includes(scope)) {
fail(ErrorCode.INVALID_ARGS, `Invalid scope "${scope}". Use: fiat | crypto | all`);
}
const includeFiat = scope !== "crypto";
const includeCrypto = scope !== "fiat";
const result: Record<string, any> = {};
if (includeFiat) {
const res = await apiGet("/v1/fiat-account");
assertOk(res, "Fiat balance fetch");
result.fiat = filterFiat(res.data.account.balances);
}
if (includeCrypto) {
const [cryptoRes, portfolioRes] = await Promise.all([
apiGet("/v1/crypto-account"),
apiGet("/v1/portfolio"),
]);
assertOk(cryptoRes, "Crypto balance fetch");
result.crypto = {
note: "available for trading",
wallets: filterCrypto(cryptoRes.data.account.wallets),
};
const products = parsePortfolioProducts(portfolioRes);
if (products) {
result.portfolio_allocation = products;
}
}
success(result);
}
async function balance(symbol: string) {
if (!symbol) {
fail(ErrorCode.INVALID_ARGS, "Token symbol required. Example: npx tsx scripts/account.ts balance BTC");
}
const upper = symbol.toUpperCase();
const [cryptoRes, allocationRes] = await Promise.all([
apiGet("/v1/crypto-account"),
apiGet(`/v1/portfolio/currency_allocation?currency=${upper}`),
]);
assertOk(cryptoRes, "Crypto balance fetch");
const wallet = (cryptoRes.data.account.wallets as any[]).find(
(w: any) => w.currency.toUpperCase() === upper,
);
const result: Record<string, any> = {
currency: upper,
available: wallet?.available?.amount ?? "0",
available_note: "available for trading",
balance: wallet?.balance?.amount ?? "0",
};
const allocation = parseCurrencyAllocation(allocationRes);
if (allocation) {
result.product_allocation = allocation;
}
success(result);
}
async function tradingLimit() {
const res = await apiGet("/v1/api-keys/current");
assertOk(res, "Trading limit fetch");
const k = res.data.api_key;
const limit = parseFloat(k.weekly_trading_limit_in_usd);
const remaining = parseFloat(k.remaining_weekly_trading_limit_in_usd);
success({ used: limit - remaining, limit, remaining, currency: "USD" });
}
async function resolveSource(tradeType: string) {
const validTypes = ["purchase", "sale", "exchange"];
if (!validTypes.includes(tradeType)) {
fail(ErrorCode.INVALID_ARGS, `Invalid trade type "${tradeType}". Use: purchase | sale | exchange`);
}
const walletType = tradeType === "purchase" ? "fiat" : "crypto";
if (walletType === "fiat") {
const res = await apiGet("/v1/fiat-account");
assertOk(res, "Fiat balance fetch");
emitResolveResult(filterFiat(res.data.account.balances), walletType);
} else {
const res = await apiGet("/v1/crypto-account");
assertOk(res, "Crypto balance fetch");
emitResolveResult(filterCrypto(res.data.account.wallets), walletType);
}
}
function emitResolveResult(funded: any[], walletType: string): never {
if (funded.length === 1) {
success({ status: "SELECTED", currency: funded[0].currency, walletType });
} else if (funded.length > 1) {
success({ status: "AMBIGUOUS", options: funded.map((w: any) => w.currency), walletType });
} else {
success({ status: "EMPTY", walletType });
}
}
async function revokeKey() {
const res = await apiPost("/v1/api-keys/self-revoke", {});
const errorCode = res.data?.error || res.data?.code;
if (errorCode === "api_key_not_found") {
fail(ErrorCode.API_KEY_NOT_FOUND, "API key not found — it may have already been revoked or does not exist.");
}
if (errorCode === "key_not_active") {
fail(ErrorCode.API_KEY_NOT_FOUND, "API key is not active — it has been revoked or expired.");
}
if (res.status !== 200 || res.data?.ok === false) {
const apiMsg = res.data?.error_message || res.data?.message;
const detail = apiMsg && errorCode ? `${errorCode}: ${apiMsg}` : errorCode || apiMsg || `HTTP ${res.status}`;
fail(ErrorCode.API_ERROR, `Kill switch request failed: ${detail}`);
}
success({ revoked: true });
}
// ---------------------------------------------------------------------------
// CLI router
// ---------------------------------------------------------------------------
const USAGE = `Usage: npx tsx scripts/account.ts <command> [args]
Commands:
balances [fiat|crypto|all] Filtered non-zero balances (default: all)
balance <SYMBOL> Single token balance lookup
trading-limit Weekly trading limit info
resolve-source <type> Find funded wallets (purchase|sale|exchange)
revoke-key Revoke API key (kill switch)`;
run(async () => {
const [command, arg] = process.argv.slice(2);
switch (command) {
case "balances":
return balances(arg || "all");
case "balance":
return balance(arg);
case "trading-limit":
return tradingLimit();
case "resolve-source":
return resolveSource(arg);
case "revoke-key":
return revokeKey();
default:
fail(ErrorCode.INVALID_ARGS, command ? `Unknown command "${command}".\n\n${USAGE}` : USAGE);
}
});
import { apiGet, assertOk } from "./lib/api.js";
import { ErrorCode, fail, run, success } from "./lib/output.js";
// ---------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------
async function search(paramsJson: string) {
if (!paramsJson) {
fail(
ErrorCode.INVALID_ARGS,
`JSON params required. Example: npx tsx scripts/coins.ts search '{"keyword":"BTC","sort_by":"rank","sort_direction":"asc","native_currency":"USD","page_size":10}'`,
);
}
let params: any;
try {
params = JSON.parse(paramsJson);
} catch {
fail(ErrorCode.INVALID_ARGS, `Invalid JSON: ${paramsJson}`);
}
const qs = new URLSearchParams();
for (const [key, val] of Object.entries(params)) {
if (val !== undefined && val !== null) qs.set(key, String(val));
}
const path = `/v1/crypto/coins?${qs.toString()}`;
const res = await apiGet(path);
assertOk(res, "Coin search");
success({
coins: res.data.coins,
pagination: res.data.pagination,
});
}
// ---------------------------------------------------------------------------
// CLI router
// ---------------------------------------------------------------------------
const USAGE = `Usage: npx tsx scripts/coins.ts <command> [args]
Commands:
search '<json>' Search coins by keyword, sort, pagination`;
run(async () => {
const [command, arg] = process.argv.slice(2);
switch (command) {
case "search":
return search(arg);
default:
fail(ErrorCode.INVALID_ARGS, command ? `Unknown command "${command}".\n\n${USAGE}` : USAGE);
}
});
import readline from "node:readline";
import { apiGet, apiPost, assertOk } from "./lib/api.js";
import { ErrorCode, fail, run, success } from "./lib/output.js";
// ---------------------------------------------------------------------------
// Helper: TOTP prompt
// ---------------------------------------------------------------------------
async function promptTotp(): Promise<string> {
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
return new Promise((resolve) => {
rl.question("TOTP code required. Enter your 6-digit authenticator code: ", (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
// ---------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------
async function discover() {
const accountRes = await apiGet("/v1/fiat-account");
assertOk(accountRes, "Fiat account fetch");
const balances: any[] = accountRes.data.account?.balances ?? [];
if (balances.length === 0) {
success({ currencies: [] });
return;
}
const currencies = [];
for (const bal of balances) {
const ccy = bal.currency;
const balance = bal.amount?.amount ?? "0";
const netRes = await apiGet(`/v1/fiat/payment-networks?currency=${encodeURIComponent(ccy)}`);
if (netRes.status !== 200 || netRes.data.ok !== true) {
currencies.push({ currency: ccy, balance, deposit: [], withdrawal: [] });
continue;
}
const networks: any[] = netRes.data.available_payment_networks ?? [];
const net = networks.find((n: any) => n.currency === ccy);
if (!net) {
currencies.push({ currency: ccy, balance, deposit: [], withdrawal: [] });
continue;
}
currencies.push({
currency: ccy,
balance,
deposit: net.deposit_push_payment_networks ?? [],
withdrawal: net.withdrawal_payment_networks ?? [],
});
}
success({ currencies });
}
async function paymentNetworks(currency: string) {
if (!currency) {
fail(ErrorCode.INVALID_ARGS, "Currency required. Example: npx tsx scripts/fiat.ts payment-networks USD");
}
const res = await apiGet(`/v1/fiat/payment-networks?currency=${encodeURIComponent(currency)}`);
assertOk(res, `Payment networks fetch for ${currency}`);
success(res.data.available_payment_networks);
}
async function depositMethods(currency: string, depositMethod: string) {
if (!currency) {
fail(ErrorCode.INVALID_ARGS, "Currency required. Example: npx tsx scripts/fiat.ts deposit-methods USD sepa");
}
if (!depositMethod) {
fail(ErrorCode.INVALID_ARGS, "Deposit method required. Example: npx tsx scripts/fiat.ts deposit-methods USD sepa");
}
const res = await apiGet(
`/v1/fiat/deposit-methods?currency=${encodeURIComponent(currency)}&deposit_method=${encodeURIComponent(depositMethod)}`,
);
assertOk(res, `Deposit methods fetch for ${currency} ${depositMethod}`);
success(res.data.deposit_methods);
}
async function emailDepositInfo(currency: string, vibanType: string) {
if (!currency) {
fail(ErrorCode.INVALID_ARGS, "Currency required. Example: npx tsx scripts/fiat.ts email-deposit-info USD iban");
}
if (!vibanType) {
fail(ErrorCode.INVALID_ARGS, "VIBAN type required. Example: npx tsx scripts/fiat.ts email-deposit-info USD iban");
}
const res = await apiPost("/v1/fiat/deposit-info/email", {
currency,
viban_type: vibanType,
});
assertOk(res, `Email deposit info for ${currency} ${vibanType}`);
success(res.data.bank_info_email);
}
async function withdrawalDetails(currency: string, vibanType: string) {
if (!currency) {
fail(ErrorCode.INVALID_ARGS, "Currency required. Example: npx tsx scripts/fiat.ts withdrawal-details USD iban");
}
if (!vibanType) {
fail(ErrorCode.INVALID_ARGS, "VIBAN type required. Example: npx tsx scripts/fiat.ts withdrawal-details USD iban");
}
const res = await apiGet(
`/v1/fiat/withdrawal-details?currency=${encodeURIComponent(currency)}&viban_type=${encodeURIComponent(vibanType)}`,
);
assertOk(res, `Withdrawal details fetch for ${currency} ${vibanType}`);
success(res.data.details);
}
async function createWithdrawalOrder(paramsJson: string) {
if (!paramsJson) {
fail(
ErrorCode.INVALID_ARGS,
`JSON params required. Example: npx tsx scripts/fiat.ts create-withdrawal-order '{"currency":"USD","amount":"100","viban_type":"iban"}'`,
);
}
let params: any;
try {
params = JSON.parse(paramsJson);
} catch {
fail(ErrorCode.INVALID_ARGS, `Invalid JSON: ${paramsJson}`);
}
if (!params.currency || !params.amount || !params.viban_type) {
fail(ErrorCode.INVALID_ARGS, "Required fields: currency, amount, viban_type");
}
const res = await apiPost("/v1/fiat/withdrawal-orders", params);
assertOk(res, "Withdrawal order creation");
success(res.data.viban_withdrawal_order);
}
async function createWithdrawal(orderId: string) {
if (!orderId) {
fail(ErrorCode.INVALID_ARGS, "Order ID required. Example: npx tsx scripts/fiat.ts create-withdrawal <order-id>");
}
let res = await apiPost("/v1/fiat/withdrawals", { order_id: orderId });
// Check for TOTP requirement
if (res.data?.error === "totp_required") {
const otp = await promptTotp();
res = await apiPost("/v1/fiat/withdrawals", { order_id: orderId, otp });
}
assertOk(res, "Withdrawal creation");
success(res.data.viban_withdrawal);
}
async function bankAccounts(currency?: string) {
const path = currency ? `/v1/fiat/bank-accounts?currency=${encodeURIComponent(currency)}` : "/v1/fiat/bank-accounts";
const res = await apiGet(path);
assertOk(res, currency ? `Bank accounts fetch for ${currency}` : "Bank accounts fetch");
success(res.data.bank_accounts);
}
// ---------------------------------------------------------------------------
// CLI router
// ---------------------------------------------------------------------------
const USAGE = `Usage: npx tsx scripts/fiat.ts <command> [args]
Commands:
discover Cash overview (balances + payment networks)
payment-networks <currency> Available deposit/withdrawal networks
deposit-methods <currency> <deposit_method> Bank details for a deposit method
email-deposit-info <currency> <viban_type> Email deposit instructions to user
withdrawal-details <currency> <viban_type> Withdrawal quotas, fees, minimums
create-withdrawal-order '<json>' Create withdrawal order
create-withdrawal <order_id> Execute withdrawal (may prompt for TOTP)
bank-accounts [currency] List linked bank accounts`;
run(async () => {
const [command, arg1, arg2] = process.argv.slice(2);
switch (command) {
case "discover":
return discover();
case "payment-networks":
return paymentNetworks(arg1);
case "deposit-methods":
return depositMethods(arg1, arg2);
case "email-deposit-info":
return emailDepositInfo(arg1, arg2);
case "withdrawal-details":
return withdrawalDetails(arg1, arg2);
case "create-withdrawal-order":
return createWithdrawalOrder(arg1);
case "create-withdrawal":
return createWithdrawal(arg1);
case "bank-accounts":
return bankAccounts(arg1);
default:
fail(ErrorCode.INVALID_ARGS, command ? `Unknown command "${command}".\n\n${USAGE}` : USAGE);
}
});
import crypto from "node:crypto";
import os from "node:os";
import { ErrorCode, fail } from "./output.js";
export const BASE_URL = "https://wapi.crypto.com";
function getCredentials(): { apiKey: string; apiSecret: string } {
const apiKey = process.env.CDC_API_KEY;
const apiSecret = process.env.CDC_API_SECRET;
if (!apiKey || !apiSecret) {
fail(
ErrorCode.MISSING_ENV,
`CDC_API_KEY and/or CDC_API_SECRET not set. Run:\n export CDC_API_KEY="your-key"\n export CDC_API_SECRET="your-secret"`,
);
}
return { apiKey, apiSecret };
}
function getSignedHeaders(method: string, path: string, body?: unknown): Record<string, string> {
const { apiKey, apiSecret } = getCredentials();
const timestamp = Date.now().toString();
const bodyStr = body ? JSON.stringify(body) : "";
const signPayload = timestamp + method.toUpperCase() + path + bodyStr;
const signature = crypto
.createHmac("sha256", apiSecret)
.update(signPayload)
.digest("base64");
const userAgent = `Node/${process.version} ${os.platform()}/${os.release()}-cdc-clawbot/1.0`;
const headers: Record<string, string> = {
"User-Agent": userAgent,
"Cdc-Api-Key": apiKey,
"Cdc-Api-Timestamp": timestamp,
"Cdc-Api-Signature": signature,
};
if (body) {
headers["Content-Type"] = "application/json";
}
return headers;
}
interface ApiResponse {
status: number;
data: any;
}
async function request(method: string, path: string, body?: unknown): Promise<ApiResponse> {
const signPath = path.split("?")[0];
const headers = getSignedHeaders(method, signPath, body);
const url = `${BASE_URL}${path}`;
const res = await fetch(url, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
let data: any;
try {
data = await res.json();
} catch {
fail(ErrorCode.API_ERROR, `Non-JSON response from ${method} ${path} (HTTP ${res.status})`);
}
return { status: res.status, data };
}
export async function apiGet(path: string): Promise<ApiResponse> {
return request("GET", path);
}
export async function apiPost(path: string, body?: unknown): Promise<ApiResponse> {
return request("POST", path, body);
}
export function assertOk(res: ApiResponse, context: string): void {
if (res.status === 429) {
fail(
ErrorCode.RATE_LIMITED,
`${context}: Rate limit exceeded. Wait 60 seconds before retrying.`,
);
}
if (res.status !== 200 || res.data?.ok !== true) {
const apiError = res.data?.error || res.data?.code;
const apiMsg = res.data?.error_message || res.data?.message;
const detail = apiMsg && apiError ? `${apiError}: ${apiMsg}` : apiError || apiMsg || `HTTP ${res.status}`;
fail(ErrorCode.API_ERROR, `${context}: ${detail}`);
}
}
export const ErrorCode = {
MISSING_ENV: "MISSING_ENV",
API_ERROR: "API_ERROR",
INVALID_ARGS: "INVALID_ARGS",
QUOTATION_FAILED: "QUOTATION_FAILED",
EXECUTION_FAILED: "EXECUTION_FAILED",
API_KEY_NOT_FOUND: "API_KEY_NOT_FOUND",
RATE_LIMITED: "RATE_LIMITED",
UNKNOWN: "UNKNOWN",
} as const;
export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
interface SuccessOutput {
ok: true;
data: unknown;
}
interface ErrorOutput {
ok: false;
error: string;
error_message: string;
}
export function success(data: unknown): never {
const out: SuccessOutput = { ok: true, data };
console.log(JSON.stringify(out, null, 2));
process.exit(0);
}
export function fail(code: ErrorCode, message: string): never {
const out: ErrorOutput = { ok: false, error: code, error_message: message };
console.log(JSON.stringify(out, null, 2));
process.exit(1);
}
export function run(fn: () => Promise<void>): void {
fn().catch((err: unknown) => {
const message = err instanceof Error ? err.message : String(err);
fail(ErrorCode.UNKNOWN, message);
});
}
import { apiGet, apiPost, assertOk } from "./lib/api.js";
import { ErrorCode, fail, run, success } from "./lib/output.js";
// ---------------------------------------------------------------------------
// Quotation body builders
// ---------------------------------------------------------------------------
const QUOTE_PATHS: Record<string, string> = {
purchase: "/v1/crypto-purchase/quotations",
sale: "/v1/crypto-sales/quotations",
exchange: "/v1/crypto-exchange/quotations",
};
const ORDER_PATHS: Record<string, string> = {
purchase: "/v1/crypto-purchase/orders",
sale: "/v1/crypto-sales/orders",
exchange: "/v1/crypto-exchange/orders",
};
function buildQuotationBody(type: string, params: any): Record<string, any> {
switch (type) {
case "purchase":
return {
from_currency: params.from_currency,
to_currency: params.to_currency,
...(params.from_amount
? { from_amount: params.from_amount }
: { to_amount: params.to_amount }),
};
case "sale":
return {
from_currency: params.from_currency,
from_amount: params.from_amount,
to_currency: params.to_currency,
fixed_side: params.fixed_side || "from",
};
case "exchange":
return {
from: params.from_currency,
to: params.to_currency,
from_amount: params.from_amount,
side: params.side || "buy",
};
default:
fail(ErrorCode.INVALID_ARGS, `Unknown trade type: ${type}`);
}
}
// ---------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------
async function quote(type: string, paramsJson: string) {
if (!QUOTE_PATHS[type]) {
fail(ErrorCode.INVALID_ARGS, `Invalid trade type "${type}". Use: purchase | sale | exchange`);
}
if (!paramsJson) {
fail(ErrorCode.INVALID_ARGS, `JSON params required. Example: npx tsx scripts/trade.ts quote purchase '{"from_currency":"USD","to_currency":"BTC","from_amount":"100"}'`);
}
let params: any;
try {
params = JSON.parse(paramsJson);
} catch {
fail(ErrorCode.INVALID_ARGS, `Invalid JSON: ${paramsJson}`);
}
const body = buildQuotationBody(type, params);
const res = await apiPost(QUOTE_PATHS[type], body);
if (res.status !== 200 || res.data?.ok !== true) {
const apiError = res.data?.error;
const apiMsg = res.data?.error_message;
const msg = apiMsg && apiError ? `${apiError}: ${apiMsg}` : apiError || apiMsg || "Quotation request rejected.";
fail(ErrorCode.QUOTATION_FAILED, msg);
}
success(res.data.quotation);
}
async function confirm(type: string, quotationId: string) {
if (!ORDER_PATHS[type]) {
fail(ErrorCode.INVALID_ARGS, `Invalid trade type "${type}". Use: purchase | sale | exchange`);
}
if (!quotationId) {
fail(ErrorCode.INVALID_ARGS, "Quotation ID required. Example: npx tsx scripts/trade.ts confirm purchase <quotation-id>");
}
const body: Record<string, string> =
type === "exchange"
? { quotation_id: quotationId, side: "buy" }
: { quotation_id: quotationId };
const res = await apiPost(ORDER_PATHS[type], body);
if (res.status !== 200 || res.data?.ok !== true) {
const apiError = res.data?.error;
const apiMsg = res.data?.error_message;
const msg = apiMsg && apiError ? `${apiError}: ${apiMsg}` : apiError || apiMsg || "Order confirmation failed.";
fail(ErrorCode.EXECUTION_FAILED, msg);
}
success(res.data.transaction);
}
async function history() {
const res = await apiGet("/v1/transactions");
assertOk(res, "Transaction history fetch");
const txns = (res.data.transactions ?? []).slice(0, 5);
success(txns);
}
// ---------------------------------------------------------------------------
// CLI router
// ---------------------------------------------------------------------------
const USAGE = `Usage: npx tsx scripts/trade.ts <command> [args]
Commands:
quote <type> '<json>' Get quotation (purchase|sale|exchange)
confirm <type> <quotation-id> Confirm order
history Last 5 transactions`;
run(async () => {
const [command, arg1, arg2] = process.argv.slice(2);
switch (command) {
case "quote":
return quote(arg1, arg2);
case "confirm":
return confirm(arg1, arg2);
case "history":
return history();
default:
fail(ErrorCode.INVALID_ARGS, command ? `Unknown command "${command}".\n\n${USAGE}` : USAGE);
}
});
Related skills
Forks & variants (1)
Crypto Com App has 1 known copy in the catalog totaling 339 installs. They canonicalize to this original listing.
- crypto-com - 339 installs
FAQ
What operations does crypto-com-app support?
crypto-com-app supports buy, sell, swap, and exchange trades plus cash deposits and withdrawals through the Crypto.com APP API. It also queries account balances, market prices, transaction history, weekly trading limits, portfolio positions, and bank account details.
How many tokens does crypto-com-app cover?
crypto-com-app documents support for BTC, ETH, CRO, and 200+ tokens across fiat and crypto wallets on the Crypto.com APP API. Exact availability still depends on account region and APP API permissions.
Is Crypto Com App safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.