Skip to content
Skip to content

Order Lifecycle

Every trade Balchemy executes — whether triggered by a chat command, an automation rule, or an external agent — follows the same lifecycle managed by the Rust trading engine. Understanding this lifecycle helps you interpret order statuses in the Studio dashboard, debug failed trades, and design automations that react to order outcomes.


The execution pipeline

Before an order ever enters the state machine, it passes through a five-stage pipeline:

  1. Intent — The user's natural-language command or agent tool call is parsed into a fully-resolved trading intent: action, token, amount, chain, wallet, and order type.
  2. Pre-trade policy — The trading engine evaluates four policy gates: global kill switch, per-chain kill switch, circuit breaker for the primary provider, and per-user trading lock. If any gate is active, the order is rejected immediately — no database write occurs.
  3. Policy — The order is checked against the bot's risk policy: maximum position size, stop-loss thresholds, allowed token list, and any active guardrails. Violations return a clear error before execution.
  4. Execute — The order is submitted to the chain (Jupiter for Solana, 0x for EVM). The engine acquires a Redis idempotency lock for 180 seconds to prevent double-execution during retries.
  5. Verify and Notify — On-chain confirmation is received, the order is marked as filled, and the result is sent back to the originating channel (chat, webhook, or API callback).

Order states

The trading engine uses an eight-state finite state machine (FSM). Each state has a precise string value stored in the database.

StateStored asMeaning
PendingpendingCreated and awaiting execution.
Pending Approvalpending_approvalWaiting for explicit user approval before proceeding.
ExecutingexecutingBeing submitted to the chain right now.
FilledcompletedFully confirmed on-chain. Terminal state.
Partially FilledpartialSome legs executed; watching for remaining (partial-sell orders).
CancelledcancelledCancelled by user or system policy. Terminal state.
FailedfailedAll retries exhausted or a hard error occurred. Terminal state.
ExpiredexpiredLimit or DCA order passed its expiry time without triggering. Terminal state.

Only completed, cancelled, failed, and expired are terminal states. The FSM rejects any transition that is not in the allowed table — for example, you cannot re-open a completed order.


State transition table

The following table shows every valid state transition. Any event applied to a state not listed here returns an InvalidTransition error.

FromEventTo
pendingStartExecutionexecuting
pendingCancelcancelled
pendingExpireexpired
pending_approvalApprovepending
pending_approvalCancelcancelled
executingFillcompleted
executingPartialFillpartial
executingFailfailed
executingCancelcancelled
partialFillcompleted
partialCancelcancelled
failedStartExecutionexecuting (retry)

Market order flow

A standard market order (buy or sell) follows this path:

Step 1 — Command received. You type "buy 0.1 SOL of BONK" in chat, or an agent calls trade_command.

Step 2 — Intent resolved. The backend parses the command into a ParsedTradingIntent with action: "buy", token_mint: <BONK mint>, order_type: "market", and your configured wallet.

Step 3 — Policy gates pass. The trading engine checks kill switches, circuit breaker, and trading locks. All pass.

Step 4 — Order created as pending. The order document is written to MongoDB with status: "pending". An execution history entry is appended: created from web.

Step 5 — Execution lock acquired. The engine sets a Redis SETNX lock (order:<id>:executing with 180s TTL) and transitions the order to executing.

Step 6 — Swap submitted. For Solana, Jupiter is queried for a route and the swap transaction is sent. For EVM, 0x is queried and the swap is submitted.

Step 7 — On-chain confirmation. The transaction signature is received. The engine calls mark_filled, which transitions the order to completed, records the transaction signature and actual output amount, and releases the execution lock.

Step 8 — Callback sent. The backend receives the fill callback from the trading engine and notifies the originating channel (chat message, webhook event, etc.).


Approval flow (pending_approval)

Some orders require explicit approval before execution begins. An order enters pending_approval when:

  • The bot's risk policy requires confirmation above a certain dollar threshold.
  • An automation or external agent creates an order but the bot is configured to require human sign-off.

To approve a pending order:

  1. Open Studio and navigate to the bot's trading dashboard.
  2. Find the order in the Pending Orders section — it shows an "Approve" or "Cancel" action button.
  3. Click Approve. The order transitions from pending_approval to pending and immediately proceeds to execution.

To cancel a pending order:

Click Cancel on the pending order. You can also cancel via the API or the MCP tool trading_orders_cancel:

{
  "method": "tools/call",
  "params": {
    "name": "trading_orders_cancel",
    "arguments": {
      "order_id": "ORDER_ID_HERE"
    }
  }
}

Cancelling an order in pending or pending_approval transitions it to cancelled. You cannot cancel an order that is already executing — the blockchain transaction is in flight.


DCA order lifecycle

DCA (Dollar-Cost Averaging) orders split a large position into multiple smaller executions over time or at defined price intervals. Each execution leg is a separate TradingOrder document linked by a parent_order_id and tracked with dca_leg_index.

The parent DCA order remains in pending until all legs complete. Each leg follows the standard market order flow independently. When the last leg fills, the parent order transitions to completed. If any leg fails after exhausting retries, that leg is marked failed but the remaining legs continue unless you cancel the parent.

To monitor a DCA order, filter the trading dashboard by the parent order ID. All associated legs appear as child entries.


Order types

Balchemy's trading engine accepts six order types, set in the order_type field of the parsed intent:

TypeDescription
marketImmediate execution at the current best price.
limitExecute only when the token price reaches a specified target. Expires if not triggered.
dcaSplit into multiple market legs executed over time.
trailing_stopFollow price upward and execute when price drops by a configured percentage from peak.
partial_sellSell a position in multiple tranches at predefined price levels.
stop_lossSell immediately when price drops to a specified level to limit losses.

Failure scenarios

Slippage exceeded

If the on-chain swap price moves beyond your configured slippage tolerance before the transaction confirms, the transaction fails. The order transitions to failed. You can retry by approving a new order at the current price, or increase your slippage tolerance in the bot's trading configuration.

Insufficient funds

If the wallet does not have enough balance to cover the trade amount plus network fees, the execution attempt fails immediately. The order transitions to failed with an error message describing the shortfall. Fund your wallet and place a new order.

Circuit breaker open

If Jupiter (Solana) or 0x (EVM) is experiencing elevated error rates, the circuit breaker opens and new orders for that chain are rejected at the policy gate — before the pending state is even created. You will see an error message: "Circuit breaker is open for provider: jupiter." The circuit breaker closes automatically when error rates normalize.

Kill switch active

If an administrator activates the global or per-chain kill switch, all new orders are rejected. This is an emergency control used during severe market events or platform maintenance. Existing orders in executing state continue to completion; only new order creation is blocked.

Retry behavior

When an order in executing transitions to failed, the engine checks whether retry_count < max_retries (default maximum: 3). If retries remain, the order is automatically reset to pending and re-queued for execution. Each retry is recorded in the execution history with a retry_scheduled entry. After all retries are exhausted, the order stays in failed permanently.


Checking order status

In Studio: Navigate to your bot and open the Trading tab. The order table shows real-time status with color-coded badges. Click any row to open the order detail drawer, which displays the full execution history, transaction signature, actual output, fees, and error messages if applicable.

Via API (MCP): Use the trading_orders_list tool to fetch orders by status:

{
  "method": "tools/call",
  "params": {
    "name": "trading_orders_list",
    "arguments": {
      "status": "pending",
      "limit": 20
    }
  }
}

The response includes the full order document for each matching record, including the execution history array that shows every state transition with timestamps.

Via REST API: Send GET /api/trading/orders?status=completed&limit=50 with your session token. See API Endpoints for the full parameter reference.


Execution history

Every order maintains an execution_history array — an append-only audit trail of every state transition. Each entry contains:

FieldDescription
statusThe step name (e.g., created, approved, executing, completed, failed, retry_scheduled).
timestampUTC ISO timestamp of the transition.
detailsHuman-readable description of what happened.
tx_signatureTransaction signature, present only on fill events.
errorError message, present only on fail events.

The execution history is read-only and append-only — it cannot be modified after creation. Use it as your audit trail when investigating order issues.


  • Solana Trading — chain-specific execution details for Solana
  • EVM Trading — chain-specific execution details for Base and Ethereum
  • Trading Dashboard — how to view and manage orders in Studio
  • Tool Catalogtrading_orders_list, trading_orders_approve, trading_orders_cancel
  • Glossary — definitions for circuit breaker, kill switch, slippage, DCA, and more
Connection lost. Retrying...