trade-worker Isolate Profile

Comprehensive engineering specification for the Hoox Trade Execution Worker, covering multi-exchange client architectures, HMAC signing, and D1 database ledgers.

This page

The trade-worker is the execution engine of the Hoox trading platform. Deployed as a private compute isolate behind the edge firewall, this worker is responsible for calculating order parameters, executing leverage scaling, performing cryptographic HMAC-SHA256 signature calculations, placing spot trades on Bybit, Binance, and MEXC, routing spot DEX (uniswap-ethereum, uniswap-arbitrum, jupiter-solana) through WEB3_WALLET_SERVICE POST /swap, and offloading transactional metrics. Perps are not supported.


⚡ 1. Declared Wrangler Configurations & Bindings

The trade-worker does not expose a public URL, communicating internally via V8 Service Bindings. Its wrangler.jsonc maps out its critical storage, queue, and database hooks:

jsonc
{
  "name": "trade-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-06-08",
  "compatibility_flags": ["nodejs_compat"],
  "account_id": "<CLOUDFLARE_ACCOUNT_ID>",
  "placement": {
    "mode": "smart",
  },
  "kv_namespaces": [
    {
      "binding": "CONFIG_KV",
      "id": "<KV_NAMESPACE_ID>",
    },
  ],
  "r2_buckets": [
    { "binding": "REPORTS_BUCKET", "bucket_name": "trade-reports" },
    { "binding": "SYSTEM_LOGS_BUCKET", "bucket_name": "hoox-system-logs" },
  ],
  "queues": {
    "consumers": [
      {
        "queue": "trade-execution",
        "max_retries": 3,
        "dead_letter_queue": "trade-execution-dlq",
      },
    ],
  },
  "durable_objects": {
    "bindings": [
      {
        "name": "EXCHANGE_CONNECTION_MANAGER",
        "class_name": "ExchangeConnectionManager",
      },
    ],
  },
  "services": [
    { "binding": "D1_SERVICE", "service": "d1-worker" },
    { "binding": "TELEGRAM_SERVICE", "service": "telegram-worker" },
    { "binding": "ANALYTICS_SERVICE", "service": "analytics-worker" },
    { "binding": "WEB3_WALLET_SERVICE", "service": "web3-wallet-worker" },
  ],
}

Secrets (INTERNAL_KEY_BINDING, EXCHANGE_KEY_BINDING, EXCHANGE_SECRET_BINDING, optional EXCHANGE_TESTNET_*, WALLET_EXECUTE_KEY_BINDING) are set with wrangler secret put — they are not declared in wrangler. D1 is reached over D1_SERVICE, not a local DB binding.


🔄 Position reconciliation

Fills write D1 positions via non-blocking waitUntil for latency. To recover ledger drift, POST /api/positions/reconcile (trade-execute internal auth) pulls open positions from each exchange with credentials and upserts D1 (OPEN / CLOSED). Agent housekeeping cron invokes this path automatically.

Body fieldEffect
exchangesOptional list (binance / bybit / mexc / uniswap-ethereum / uniswap-arbitrum / jupiter-solana)
testnetReconcile testnet namespace + clients
dryRunDiff only — no D1 writes

🧪 Test trading (test: true)

TopicBehavior
SchemaOptional test?: boolean on WebhookPayload and TradeQueueMessage
HostsBinance Futures testnet · Bybit testnet · MEXC rejected
CredentialsPrefer *_TESTNET_* secrets; fall back to live keys with a warn log
TransportAlways REST; live WebSocket Durable Object is never used
D1 tradesstatus = 'TEST_EXECUTED'
D1 positionsid = {exchange}-testnet-{symbol}-{side}
TelegramExchange label includes [TEST] (single notify — no queue double-send)
AnalyticsExchange blob encoded as {exchange}:test
Live WS perfWhen use_websocket=true and live, router skips REST client construction

Operator guide: Test Trading.


Spot DEX venues

Spot swaps route through WEB3_WALLET_SERVICE (web3-wallet-worker POST /swap). Spot only — perps are not supported.

NameChainKindToggleQuantityTest flag
uniswap-ethereumethereumspotexchange:uniswap-ethereum:enabledLONG spends QUOTE (ExactIn) to buy BASE; SHORT sells BASERejected (TEST_TRADING_UNSUPPORTED) — mainnet-only
uniswap-arbitrumarbitrumspotexchange:uniswap-arbitrum:enabledsameRejected — mainnet-only
jupiter-solanasolanaspotexchange:jupiter-solana:enabledsameRejected — mainnet-only

Uniswap venues send dexVersion: "v3" (ETH/USDC liquidity is V3; Arbitrum's V2 router in the wallet worker is SushiSwap). Native ETH/SOL map to the wrapped mint on contract paths. Credential source is wallet-binding (mesh key), not CEX API secrets. Symbols must be BASE/QUOTE (slash). Unset venue toggles mean enabled; "false" skips the venue.

Operator guide: DEX spot swaps. hoox setup writes WALLET_EXECUTE_KEY_BINDING automatically.


🔀 2. The Provider-Based ExchangeRouter Pattern

To support multiple centralized exchanges with different API schemas while maintaining a clean code structure, trade-worker implements a Provider Composition Pattern:

A. Generic Exchange Provider Interface

The client abstraction is defined inside the shared monorepo package @hoox-sh/hoox-shared/types:

typescript
export interface IExchangeProvider<TClient, TEnv> {
  readonly name: string;
  createClient(env: TEnv, options?: { testnet?: boolean }): TClient;
  hasCredentials(env: TEnv): boolean;
  /** False for exchanges with no public REST sandbox (e.g. MEXC). */
  readonly supportsTestTrading?: boolean;
}

B. Dynamic Runtime Routing

The ExchangeRouter evaluates the incoming symbol and parses settings in CONFIG_KV in sub-milliseconds:

  1. Default Path: Routes trades to the default CEX declared in exchanges:default_routing (typically bybit).
  2. Dynamic Symbol Redirects: Parses overrides in KV (e.g. exchanges:routing:SOLUSDT = binance). If present, the router bypasses Bybit and instantiates the BinanceProvider instantly without redeploying code.
  3. Test trading: When the payload includes "test": true, the router creates the client with testnet: true (Binance → testnet.binancefuture.com, Bybit → api-testnet.bybit.com). MEXC and all spot DEX venues reject with TEST_TRADING_UNSUPPORTED. Test mode always uses REST (live WebSocket Durable Objects are skipped). Credentials prefer EXCHANGE_TESTNET_* secrets (legacy BINANCE_TESTNET_* / BYBIT_TESTNET_* still resolve as fallbacks), then live bindings.
  4. Live WS perf: when exchange:{name}:use_websocket=true and the trade is live, the router skips REST client construction entirely and hands off to the Durable Object.

🔌 3. Internal REST API Specification

Note: For the canonical endpoint directory with full request/response examples across all workers, see /docs/devops/api/endpoints.

Internal routes (mesh auth; prefer TRADE_EXECUTE_KEY_BINDING, else INTERNAL_KEY_BINDING):

MethodPathPurpose
POST/webhookGateway / pyne / email dispatch
POST/processLegacy / queue-shaped body
GET/POST/api/signalsSignal list / insert
GET/api/system-logsOperator SSE log feed
POST/api/positions/reconcileExchange ↔ D1 position sync
GET/reportReport payload for report-worker
GET/healthLiveness

A. Process Order Pipeline

Invoked by the hoox gateway (POST /webhook) or the TRADE_QUEUE consumer.

  • Endpoint: /process (legacy alias of the same execution path)
  • Method: POST
  • Headers: X-Internal-Auth-Key: <INTERNAL_KEY_BINDING>
  • JSON Payload:
    json
    {'{'}
      "requestId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
      "payload": {'{'}
        "exchange": "bybit",
        "action": "LONG",
        "symbol": "BTCUSDT",
        "quantity": 0.005,
        "leverage": 10,
        "test": true
      {'}'}
    {'}'}
    
  • Success Response (200 OK):
    json
    {'{'}
      "success": true,
      "result": {'{'}
        "orderId": "18049284739",
        "status": "Filled",
        "price": 68425.5
      {'}'},
      "error": null
    {'}'}
    

🛡️ 4. Standardized Exception Handling

All execution rejects and validation failures are intercepted by the trade-worker error middleware and formatted using the shared Errors factory from @hoox-sh/hoox-shared/errors:

typescript
import { Errors } from "@hoox-sh/hoox-shared/errors";

// 1. Parameter Validation Failure
if (quantity <= 0) {
  return Errors.badRequest("Quantity parameter must be greater than zero.");
}

// 2. Exchange Signature Timeout
if (timestampExpired) {
  return Errors.unauthorized(
    "Timestamp verification failed. Check system NTP sync."
  );
}

// 3. API Execution Rejects
try {
  await exchange.placeOrder(order);
} catch (err: any) {
  return Errors.internal(`Exchange API Reject: ${err.message}`);
}

Tip

If the exchange API rejects an order due to account rate-limiting, the queue consumer automatically returns a retry flag. Cloudflare® Queues will back off and re-route the batch at intervals starting at 30 seconds, protecting your strategy from missed fills.

🔗 Next Steps