Order Lifecycle
Every trade Balchemy executes — whether triggered by a Hub command, an agent runtime, MCP, or an approved API path — follows the same lifecycle managed by the Rust trading engine. Understanding this lifecycle helps you interpret order statuses, debug failed trades, and design rules that react to order outcomes.
The execution pipeline
Before an order ever enters the state machine, it passes through a five-stage pipeline:
- 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.
- 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.
- Policy — The order is checked against the agent's risk policy: maximum position size, stop-loss thresholds, allowed token list, and any active guardrails. Violations return a clear error before execution.
- 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.
- 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.
| State | Stored as | Meaning |
|---|---|---|
| Pending | pending | Created and awaiting execution. |
| Pending Approval | pending_approval | Waiting for explicit user approval before proceeding. |
| Executing | executing | Being submitted to the chain right now. |
| Filled | completed | Confirmed on-chain and reconciled. Terminal state. |
| Partially Filled | partial | Some legs executed; watching for remaining (partial-sell orders). |
| Cancelled | cancelled | Cancelled by user or system policy. Terminal state. |
| Failed | failed | An execution attempt failed. Terminal only once retry is exhausted or suppressed. |
| Expired | expired | Limit or DCA order passed its expiry time without triggering. Terminal state. |
completed, cancelled, and expired are terminal. failed is conditionally terminal: while retries remain and no transaction has been broadcast, the engine can move a failed order back into executing, as the transition table below shows. Once retry is exhausted or suppressed, failed is terminal. 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.
| From | Event | To |
|---|---|---|
pending | StartExecution | executing |
pending | Cancel | cancelled |
pending | Expire | expired |
pending_approval | Approve | pending |
pending_approval | Cancel | cancelled |
executing | Fill | completed |
executing | PartialFill | partial |
executing | Fail | failed |
executing | Cancel | cancelled |
executing | RequireApproval | pending_approval |
partial | Fill | completed |
partial | Cancel | cancelled |
failed | StartExecution | executing (retry) |
The failed → executing transition is guarded, not automatic. It is only taken when retries remain and no transaction was broadcast for the order. See Retry behavior below.
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 — Broadcast, then confirmation. Receiving a transaction signature means the swap was broadcast, not that it succeeded. Once the transaction is confirmed on-chain, the engine calls mark_filled, which transitions the order to completed, records the transaction signature and actual output amount, and releases the execution lock. A signature on its own never promotes an order to completed; if the broadcast outcome is not yet known, the order waits for reconciliation instead.
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 agent's risk policy requires confirmation above a certain dollar threshold.
- A Hub agent, rule, or MCP caller creates an order but the configured policy requires human sign-off.
To approve a pending order:
- Open Hub and navigate to the relevant agent's Trades or Activity view.
- Find the order in the Pending Orders section — it shows an "Approve" or "Cancel" action button when the authorized product flow supports that action.
- Click Approve. The order transitions from
pending_approvaltopendingand 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.
A cancel on an order that is already executing is also accepted, and it marks the order cancelled in Balchemy. That is a local state change only: it does not recall a transaction that has already been broadcast to the chain. A broadcast transaction resolves on-chain independently and may still confirm, fail, or require reconciliation after the order reads cancelled. Treat a cancelled executing order as an outcome that is not yet settled — check the transaction signature on-chain rather than assuming the trade did not happen.
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.
The order schema declares parent_order_id and dca_leg_index fields, but the engine does not currently populate them — every leg is written with both left empty. Legs are therefore not linked to their parent in the stored data, and you cannot group them by parent order ID.
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, review the individual leg orders in the trading dashboard or through trading_orders_list. Because the parent linkage is not stored, the legs do not appear as child entries under the parent — identify them by token, order type, and execution time rather than by a parent order ID.
Order types
Balchemy's trading engine accepts six order types, set in the order_type field of the parsed intent:
| Type | Description |
|---|---|
market | Immediate execution at the current best price. |
limit | Execute only when the token price reaches a specified target. Expires if not triggered. |
dca | Split into multiple market legs executed over time. |
trailing_stop | Follow price upward and execute when price drops by a configured percentage from peak. |
partial_sell | Sell a position in multiple tranches at predefined price levels. |
stop_loss | Sell 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 adjust slippage through the authorized agent/rules flow.
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. The kill switch blocks new order creation; it does not recall orders already in executing. A transaction that has already been broadcast resolves on-chain independently of the kill switch and may confirm, fail, or require reconciliation.
Retry behavior
When an order in executing transitions to failed, the engine decides whether to retry using two conditions, not one:
retry_count < max_retries(default maximum: 3), and- no transaction was broadcast for this order — the order's transaction signature is still unset.
Both must hold. When they do, the order is reset to pending and a retry_scheduled entry is appended to the execution history.
What happens next depends on the order type. The scheduler that re-picks up pending orders polls for limit orders specifically, so a reset limit order is re-evaluated on a later sweep. A reset market order has no scheduled consumer: it sits in pending rather than resuming on its own. Check the order in Hub and place a fresh order at the current price if you still want the trade.
Warning: If a transaction signature is already recorded, retry is suppressed even when retries remain. A broadcast transaction may still confirm on-chain, so dispatching a second swap would risk executing the trade twice. The order stays in
failed, its error message is annotated(tx broadcast — retry suppressed, awaiting reconciliation), and the transaction reconciliation job — not the retry path — owns promotion tocompletedfor broadcast orders.
A failed order that carries a transaction signature is therefore not proof that no trade occurred; it means the outcome is not yet reconciled. Do not resubmit such an order manually. Check the signature on-chain and let reconciliation resolve it. Once retries are exhausted or suppressed, the order stays in failed permanently.
Checking order status
In Hub: Navigate to the agent and open the Trades or Activity section. The order view shows status, execution history, transaction signature, actual output, fees, and error messages where available.
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:
| Field | Description |
|---|---|
status | The step name (e.g., created, approved, executing, completed, failed, retry_scheduled). |
timestamp | UTC ISO timestamp of the transition. |
details | Human-readable description of what happened. |
tx_signature | Transaction signature, present only on fill events. |
error | Error 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.
Related pages
- Solana Trading — chain-specific execution details for Solana
- EVM Trading — chain-specific execution details for Base and Ethereum
- Hub Overview — the active agent operations surface
- Tool Catalog —
trading_orders_list,trading_orders_approve,trading_orders_cancel - Glossary — definitions for circuit breaker, kill switch, slippage, DCA, and more