Closed Beta — Demo trading only. No real funds. Help us find bugs!
    BETA
    FeaturesPricingAbout
    Sign InJoin Beta

    Developer Docs

    Getting StartedBot EnginesExchange IntegrationsArchitectureSecurityDatabaseDeploymentTesting

    User Guide

    Getting StartedTrading BotsAI BotDCA BotSignal BotArbitrage BotPump BotScalping BotGrid BotCombo BotTradingDeFiPortfolioMarketplaceSubscriptionsSettingsAdmin

    Exchange Integrations

    Overview

    BlockbotX supports two cryptocurrency exchanges for automated trading:

    • Binance -- the largest global exchange by volume
    • OKX -- a major exchange with strong derivatives and demo trading support

    All exchange API keys are encrypted at rest using AES-256-GCM and stored in the ExchangeConnection database model. The schema enforces @@unique([userId, exchange]) to prevent duplicate connections per user per exchange.

    Both exchanges support testnet/demo mode, allowing users to paper-trade without risking real funds. The isTestnet flag on each ExchangeConnection record determines whether live or demo endpoints are used.

    Key files:

    ComponentPath
    Binance clientlib/binance/client.ts
    Binance tradinglib/binance/trading.ts
    Binance WebSocketlib/binance/websocket.ts
    OKX clientlib/okx/client.ts
    OKX symbol mappinglib/okx/symbol-map.ts
    Encryption modulelib/encryption/api-keys.ts
    Connect API routeapp/api/exchange/connect/route.ts
    Test-connection routeapp/api/exchange/test-connection/route.ts

    Binance Integration

    Client (lib/binance/client.ts)

    Uses the binance-api-node npm package. There are two client types:

    Public client (singleton) -- created via getBinanceClient(). This client requires no API keys and is used for Binance-specific public endpoints:

    • exchangeInfo() -- trading pairs, lot sizes, filters (cached 1 hour)
    • book({ symbol, limit }) -- order book depth
    • candles({ symbol, interval, limit }) -- candlestick/kline data
    • trades({ symbol, limit }) -- recent trades
    • ping() -- connectivity test

    User client (per-user) -- created via getUserBinanceClient(apiKey, apiSecret, isTestnet). This client is authenticated and used for:

    • accountInfo() -- account balances and permissions
    • order() -- place orders (market, limit, stop-loss)
    • cancelOrder() -- cancel a specific order
    • getOrder() -- check order status
    • openOrders() -- list open orders
    • allOrders() -- list all orders for a symbol
    • myTrades() -- trade history
    • cancelOpenOrders() -- cancel all open orders for a symbol

    Testnet support: When BINANCE_TESTNET=true (env) or isTestnet=true (per-user), the client routes to:

    • HTTP: https://demo-api.binance.com (instead of https://api.binance.com)
    • WebSocket: wss://demo-stream.binance.com (instead of wss://stream.binance.com:9443/ws)

    Price fetching: The getPrice(), getPrices(), and get24hrTicker() functions do NOT use the Binance API. They use CoinGecko via the PriceOracle (lib/defi/price-oracle.ts) with a 5-second Redis cache. This avoids Binance rate limits for price data that does not require exchange-specific precision.

    Mock client for CI/test: When process.env.CI or NODE_ENV=test, getBinanceClient() returns a mock client with realistic static data for all public endpoints. This allows API route tests to verify response structure without live credentials.

    Credential retrieval: The private getUserBinanceCredentials(userId) function fetches the user's active Binance ExchangeConnection from the database, decrypts the API key and secret, and returns them along with the isTestnet flag. Higher-level functions like placeBinanceOrder(), cancelBinanceOrder(), getOrderStatus(), and getBinanceBalance() use this internally so callers only need to pass a userId.

    Trading (lib/binance/trading.ts)

    Provides typed wrappers around Binance order operations:

    Order placement:

    placeOrder(apiKey, apiSecret, params, isTestnet)
    placeMarketOrder(apiKey, apiSecret, symbol, side, quantity?, quoteOrderQty?, isTestnet)
    placeLimitOrder(apiKey, apiSecret, symbol, side, quantity, price, timeInForce, isTestnet)
    
    • PlaceOrderParams supports types: MARKET, LIMIT, STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT
    • Market buy orders can use quoteOrderQty to specify the amount in quote asset (e.g., spend 100 USDT)
    • Limit orders default to GTC (Good Till Cancelled) time-in-force

    Order management:

    cancelOrder(apiKey, apiSecret, params, isTestnet)
    cancelAllOrders(apiKey, apiSecret, symbol)
    getOrderStatus(apiKey, apiSecret, symbol, orderId)
    getOpenOrders(apiKey, apiSecret, symbol?)
    getAllOrders(apiKey, apiSecret, symbol, limit)
    getAccountTrades(apiKey, apiSecret, symbol, limit)
    

    Order fill polling:

    waitForOrderFill(apiKey, apiSecret, symbol, orderId, maxAttempts, intervalMs, isTestnet)
    // Returns: { filled: boolean; order: BinanceOrderResult }
    

    Polls the order status at intervalMs intervals (default 1000ms) up to maxAttempts (default 10). Returns immediately on terminal statuses: FILLED, CANCELED, REJECTED, EXPIRED.

    WebSocket (lib/binance/websocket.ts)

    The BinanceWebSocketManager class provides real-time market data streaming via the binance-api-node WebSocket interface. Accessed as a singleton via getBinanceWebSocketManager().

    Available subscriptions:

    MethodDataReturns
    subscribeToPriceUpdates(symbol)Single symbol priceUnsubscribe fn
    subscribeToMultiplePriceUpdates(symbols)Filtered from all-tickersUnsubscribe fn
    subscribeToTickerUpdates(symbol)24hr ticker for one symbolUnsubscribe fn
    subscribeToAllTickers()All trading pair tickersUnsubscribe fn
    subscribeToKlines(symbol, interval)Candlestick updatesUnsubscribe fn

    Key interfaces:

    • PriceUpdate -- { symbol, price, timestamp }
    • TickerUpdate -- { symbol, priceChange, priceChangePercent, weightedAvgPrice, lastPrice, volume, quoteVolume, openTime, closeTime, high, low }
    • KlineUpdate -- { symbol, interval, startTime, endTime, open, high, low, close, volume, isFinal }

    Each subscription returns an unsubscribe function. Duplicate subscriptions to the same symbol are prevented. Call unsubscribeAll() to close all active streams.


    OKX Integration

    Client (lib/okx/client.ts)

    Zero-dependency implementation using raw fetch() and Node.js crypto module -- no npm packages required.

    Base URL: https://my.okx.com (NOT www.okx.com). The my.okx.com domain is required for demo/simulated trading API keys.

    Authentication signing:

    sign(timestamp, method, requestPath, body, secretKey) -> base64 string
    

    The signature is computed as: Base64(HMAC-SHA256(timestamp + METHOD + requestPath + body, secretKey))

    Auth headers (added to every authenticated request):

    HeaderValue
    OK-ACCESS-KEYAPI key
    OK-ACCESS-SIGNHMAC-SHA256 signature (base64)
    OK-ACCESS-TIMESTAMPISO 8601 timestamp
    OK-ACCESS-PASSPHRASEAPI passphrase

    Demo mode: Uses the same base URL but adds the header x-simulated-trading: 1.

    Core request function:

    okxRequest<T>(method, path, credentials?, body?) -> Promise<T>
    

    Handles authentication headers, demo-mode header, HTTP error checking, and OKX-specific error code validation (code must be "0" for success).

    Public endpoints (no auth needed):

    • getOkxTickers() -- fetches all SPOT tickers via /api/v5/market/tickers?instType=SPOT. Returns array of { instId, last, vol24h, high24h, low24h, open24h }.
    • getOkxPrices(symbols?) -- returns Map<instId, price>. If symbols array provided, filters to those; otherwise returns all.

    Private endpoints (auth required):

    • getOkxAccountBalance(credentials) -- fetches account balance via /api/v5/account/balance. Returns array of OkxBalance ({ ccy, availBal, frozenBal, bal }).
    • testOkxConnection(credentials) -- validates credentials by attempting a balance fetch. Returns true on success, false on failure.

    Credentials interface:

    interface OkxCredentials {
      apiKey: string;
      apiSecret: string;
      passphrase: string;  // Required -- OKX always requires a passphrase
      isTestnet: boolean;
    }
    

    Symbol Format (lib/okx/symbol-map.ts)

    Binance and OKX use different symbol formats:

    ExchangeFormatExample
    BinanceConcatenatedBTCUSDT
    OKXHyphen-separatedBTC-USDT

    Conversion functions:

    binanceToOkx("BTCUSDT")  // returns "BTC-USDT"
    okxToBinance("BTC-USDT") // returns "BTCUSDT"
    

    binanceToOkx() splits on known quote assets: USDT, USDC, BUSD, BTC, ETH, EUR, GBP. If the input already contains a hyphen, it is returned as-is.

    okxToBinance() simply removes the hyphen.


    API Key Encryption

    All exchange credentials are encrypted using AES-256-GCM (authenticated encryption) in lib/encryption/api-keys.ts.

    Configuration

    • Requires a 32-character ENCRYPTION_KEY environment variable
    • The key is lazily resolved on first use (not at import time), allowing next build to proceed without the key in the environment
    • If the key is missing or not exactly 32 characters, the application throws a fatal error

    Encryption Format

    Encrypted values are stored as a colon-separated string:

    iv:authTag:encryptedData
    

    All three components are hex-encoded:

    • IV (Initialization Vector): 12 bytes (96 bits), randomly generated per encryption call via crypto.randomBytes(12). A unique IV per encryption ensures identical plaintext produces different ciphertext.
    • Auth Tag: 16 bytes, produced by GCM mode. Provides tamper detection -- if the ciphertext or IV is modified, decryption fails.
    • Encrypted Data: The AES-256-GCM ciphertext of the original API key/secret/passphrase.

    Functions

    encrypt(text: string): string           // Returns "iv:authTag:encryptedData"
    decrypt(encryptedText: string): string  // Parses format and decrypts
    hash(text: string): string             // SHA-256 one-way hash
    verifyHash(text, hashedText): boolean  // Constant-time comparison (timing-attack safe)
    generateEncryptionKey(length): string  // Generate a random key (default 32 chars)
    

    Storage in Database

    The ExchangeConnection model stores:

    FieldDescription
    encryptedApiKeyEncrypted API key (both exchanges)
    encryptedSecretKeyEncrypted API secret (both exchanges)
    encryptedPassphraseEncrypted passphrase (OKX only, nullable)

    Keys are decrypted only at the point of use (when making an API call to the exchange) and are never held in memory longer than necessary. The Winston logger's redactFormat automatically filters any field containing key, secret, password, or token to prevent accidental credential exposure in logs.


    Connection Flow

    1. User Submits Credentials

    The user provides their API key, API secret, and (for OKX) passphrase via the UI. They also select whether these are testnet/demo credentials.

    2. Test Connection (POST /api/exchange/test-connection)

    Before saving, credentials can be tested without persisting them:

    • Authentication: Requires authenticated user via getAuthUser(req).
    • Validation: Input validated with Zod schema (exchange, apiKey, apiSecret, optional passphrase, isTestnet).
    • OKX passphrase check: If exchange is okx and no passphrase is provided, returns a 400 error.
    • Exchange-specific testing:
      • Binance: Calls testUserConnection() which invokes client.accountInfo().
      • OKX: Calls testOkxConnection() which invokes getOkxAccountBalance().
    • Balance verification: On successful connection, attempts to fetch balances to verify read permissions. A balance fetch failure does not invalidate the connection.
    • Error messages: Provides specific error messages for common failures (invalid API key, signature error, IP whitelist issue, timestamp sync).

    3. Save Connection (POST /api/exchange/connect)

    Encrypts and persists the credentials:

    • Authentication: Requires authenticated user.
    • Validation: Same Zod schema as test-connection.
    • Live validation: Calls the exchange API to verify credentials before saving (same logic as test-connection).
    • Encryption: Encrypts apiKey, apiSecret, and passphrase (if present) using encrypt().
    • Upsert logic: Checks for an existing connection with the same userId + exchange. If found, updates it; otherwise, creates a new record.
    • Audit logging: Creates an AuditLog entry for exchange_connected or exchange_updated.

    4. Connection Active

    Once saved, the connection is available for bot trading. Bot engines (arbitrage, DCA, etc.) retrieve the user's ExchangeConnection, decrypt the credentials at runtime, and execute trades.

    5. Disconnecting (DELETE /api/exchange/connect?id=<connectionId>)

    • Verifies the connection belongs to the authenticated user.
    • Deletes the ExchangeConnection record.
    • Creates an audit log entry for exchange_disconnected.

    6. Listing Connections (GET /api/exchange/connect)

    Returns all connections for the authenticated user, excluding encrypted credential fields. Only returns: id, exchange, isActive, createdAt, updatedAt.


    Adding a New Exchange

    Follow this pattern to integrate a new exchange:

    Step 1: Create the Client Module

    Create lib/{exchange}/client.ts with:

    • An authentication signing function appropriate for the exchange (HMAC-SHA256, RSA, etc.)
    • A generic request function that handles auth headers, error codes, and demo-mode switching
    • Public market data functions (tickers, prices) -- no auth required
    • Private account functions (balance, order placement) -- auth required
    • A testConnection() function that validates credentials by making a lightweight authenticated call
    • Export a credentials interface specific to the exchange

    Reference lib/okx/client.ts for a zero-dependency approach, or use an npm package like lib/binance/client.ts does with binance-api-node.

    Step 2: Create the Symbol Map

    Create lib/{exchange}/symbol-map.ts with:

    • A conversion function from the exchange's symbol format to the internal format (Binance-style concatenated)
    • A conversion function from the internal format to the exchange's format
    • A list of known quote assets used for splitting pairs

    Step 3: Add Exchange to Supported List

    Update the Zod validation schemas in the API routes:

    • app/api/exchange/connect/route.ts -- add the exchange name to z.enum(["binance", "okx", "{new_exchange}"])
    • app/api/exchange/test-connection/route.ts -- same enum update

    Step 4: Update Connection Handling

    In both API routes, add else if (exchange === "{new_exchange}") branches for:

    • Connection testing (calling the new exchange's testConnection())
    • Balance fetching (mapping the exchange's balance format to { asset, free, locked })

    Step 5: Handle Passphrase or Additional Fields

    If the new exchange requires additional credentials (like OKX's passphrase):

    • Add validation in the connect route (e.g., if (exchange === "{new_exchange}" && !someField))
    • Encrypt and store in the appropriate ExchangeConnection field
    • If the existing schema fields are insufficient, add new encrypted fields to the Drizzle schema in drizzle/schema/ and run pnpm db:generate followed by pnpm db:migrate

    Step 6: Add Price Fetching to Bot Engines

    If the exchange is to participate in arbitrage:

    • Update lib/bot/engine/arbitrage-bot.ts to fetch prices from the new exchange's public API
    • Use the symbol map to convert between formats when comparing prices across exchanges

    Step 7: Update the UI

    • Add the new exchange as an option in the connections tab
    • Add any exchange-specific fields (e.g., passphrase input) that are conditionally rendered
    • Update the exchange icon/logo assets

    Step 8: Create a Barrel Export

    Create lib/{exchange}/index.ts that re-exports from client.ts, symbol-map.ts, and any other modules. This follows the project convention of barrel exports for all lib domains.

    AI-powered crypto trading platform in closed beta. 8 bot engines, DeFi integration, and demo trading.

    Product

    • Features
    • Pricing
    • About
    • Join Beta

    Resources

    • Documentation
    • API Reference
    • Support
    • Blog
    • Status

    Legal

    • Terms of Service
    • Privacy Policy
    • Disclaimer

    © 2026 BlockbotX. All rights reserved.

    Built with AI · Powered by Next.js