# Order Placement

## Overview

Ethereal supports **market orders** for immediate execution and **limit orders** for execution at specific price levels. Before placing an order, your subaccount must have sufficient margin to cover the position. Once an order is accepted, required margin is locked while the order remains open.

*Most integrations follow this flow:*

1. Fetch the subaccount you will trade from.
2. Fetch product metadata from `GET /v1/product`.
3. Build the order payload using the product’s `onchainId`, `engineType`, `tickSize`, `lotSize`, min/max price, and max quantity.
4. Generate a fresh `nonce` and `signedAt` timestamp.
5. Sign the EIP-712 order message.
6. Submit the order.
7. Track the order by `id` or your own `clientOrderId`.
8. Cancel, replace, or batch-update orders as needed.

For detailed information about order mechanics and advanced order types, [refer to the trading documentation](/trading/perpetual-futures/order-types.md).

## Lifecycle

Every order follows a complete lifecycle from initial creation through final disposition within the trading system. You can monitor an order's current state by checking the `status` field in the when querying for orders, which provides real-time updates as the order progresses through different stages. Understanding these status transitions helps you track order execution and manage your trading strategy effectively.

The order status system includes several key states that indicate where your order stands in the execution process.

<table><thead><tr><th width="173.66015625">Status</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>NEW</code></strong></td><td>A submitted order that has successfully submitted, acknowledged and visible on the books.</td></tr><tr><td><strong><code>PENDING</code></strong></td><td>An acknowledged order that has not yet been triggered.</td></tr><tr><td><strong><code>FILLED_PARTIAL</code></strong></td><td>A partially filled order.</td></tr><tr><td><strong><code>FILLED</code></strong></td><td>A closed order that has been fully filled.</td></tr><tr><td><strong><code>CANCELED</code></strong></td><td>A canceled order that may have been partially filled or not. You can identify which state by consuming the <code>filled</code> property. A non-zero filled value and canceled status is the former whereas a zero filled is the latter.</td></tr><tr><td><strong><code>EXPIRED</code></strong></td><td>An order becomes expired when the current system time reaches or exceeds the <code>expireTime</code> timestamp that was assigned during submission, automatically removing it from the order book.</td></tr></tbody></table>

{% hint style="info" %}
After successfully submitting an order, the API returns the order's current state in real-time, including the order details and any quantity that was immediately filled during submission.
{% endhint %}

### Required Inputs

Before submitting an order, you'll need a small set of account, product, order, and signing fields. These fields are split between the request `data` object and the top-level `signature`, but the signature must be generated from the exact values you submit.

The key fields are:

* `sender`: **wallet** or **linked signer** address producing the signature.
* `subaccount`: `bytes32`-encoded subaccount name.
* `onchainId`: product ID from the product endpoint.
* `engineType`: currently use `0` for PERP.
* `side`: `0` for buy, `1` for sell.
* `quantity`: decimal string in native product units (e.g. `"1.15"`).
* `price`: required for limit orders (`0` if market orders).
* `nonce`: unique Unix timestamp in nanoseconds, sent as a string.
* `signedAt`: current Unix timestamp in seconds.
* `signature`: EIP-712 signature authorizing the action.
* `clientOrderId`: An optional reference id for client side tracking.

{% hint style="info" %}
Decimal fields support up to 9 decimal places. Send decimal values as strings so precision is not lost before the payload is signed.
{% endhint %}

### Product Rules

Product metadata controls whether an order is valid. The API will validate these rules, but integrations *should* apply them before signing so users get immediate feedback and invalid requests are not sent unnecessarily.

The most important product-level constraints are size and price increments:

* `quantity` must be greater than zero unless `close` is true.
* `quantity` must be at least `lotSize`.
* `quantity` must be a multiple of `lotSize`.
* `quantity` must not exceed `maxQuantity`.
* Limit order `price` must be positive.
* `price` and `stopPrice` must be multiples of `tickSize`.
* `price` must be between `minPrice` and `maxPrice`.

In practice, this means your integration should load the product once, cache the constraints, and validate the intended quantity and price before constructing the signed message.

### Order Types

Use `MARKET` for immediate execution. Market orders do not include `price`, `timeInForce`, or `postOnly`.

Use `LIMIT` when specifying a price. Limit orders require `price`, `timeInForce`, and `postOnly`.

The `timeInForce` value controls what happens to unfilled quantity:

* `GTD`: good-till-date. The order can rest until filled, canceled, or expired.
* `IOC`: immediate-or-cancel. Any unfilled quantity is canceled immediately.
* `FOK`: fill-or-kill. The full quantity must fill immediately or the order is rejected.

`postOnly` can only be used with `GTD` limit orders. A post-only order is rejected if it would immediately match. See [order types](/trading/perpetual-futures/order-types.md) for more information.

### Order Message Signing

Order placement uses an EIP-712 `TradeOrder` message. Cancellation uses an EIP-712 `CancelOrder` message. For a new order, the signed `TradeOrder` contains:

* `sender`
* `subaccount`
* `quantity`
* `price`
* `reduceOnly`
* `side`
* `engineType`
* `productId`
* `nonce`
* `signedAt`

The `productId` in the signed message is the product `onchainId` used in the API payload. For market orders, sign `price` as `0`. Decimal `quantity` and `price` values are represented in the signed message as 9-decimal fixed-point integers.

For cancellation, the signed `CancelOrder` is intentionally smaller:

* `sender`
* `subaccount`
* `nonce`

The order IDs or client order IDs being canceled are part of the API request, but not part of the signed cancel message. The EIP-712 domain uses name `Ethereal` and version `1`. The verifying contract is the Ethereal exchange contract for that environment. Signatures must be 65 bytes, hex encoded, with `v` equal to `27` or `28`.

Read for more information: [Message Signing](/developer-guides/trading-api/message-signing.md).

### Nonces & Timestamps

`nonce` must be a numeric string containing a Unix timestamp in nanoseconds. Do not send it as a JSON number.

`expiresAt` and `signedAt` are Unix timestamps in seconds.

Generate a fresh nonce for every signed action and keep client clocks synchronized. The API expects `signedAt` to be recent and the `nonce` to be close to the current time.

Practical rules:

* Generate a new `nonce` for each signed order or cancel request.
* Send `nonce` as a string, not a JSON number.
* Use seconds for `signedAt` and nanoseconds for `nonce`.
* If provided, `expiresAt` must be after `signedAt`.
* `expiresAt` must be in the future.
* `expiresAt` must be no more than `6,652,800` seconds after `signedAt`.

Nonce reuse is rejected. For batch orders, nonces are checked separately for new-order instructions and cancel instructions because they sign different EIP-712 message types.

### ClientOrderIDs

`clientOrderId` lets your integration track orders with your own identifier. It can be a UUID or an alphanumeric string up to 32 characters.

This field is especially useful for:

* retry-safe order placement,
* reconciliation after timeouts,
* canceling without first resolving the Ethereal order ID,
* quote replacement,
* batch workflows.

Client order IDs are scoped to a subaccount and must be unique for active orders.

### Submitting & Canceling Orders

Single-order placement and cancellation are the simplest way to integrate with Ethereal. Use them when the user is placing one order at a time, or when your system does not need to coordinate multiple order actions in the same request.

Submit a single order with:

`POST /v1/order`

* A successful request returns HTTP `201` with the assigned order `id`, optional `clientOrderId`, and a result code.
* Treat this response as the first state update for the order. If the order can rest on the book, continue monitoring it through order queries or fills.
* The `filled` field in the placement response is deprecated (see [Block Execution](/trading/perpetual-futures/block-execution.md)). Use order state and fill data for accounting.

Cancel one or more existing orders with:

`POST /v1/order/cancel`

* You can cancel by `orderIds`, `clientOrderIds`, or both. The combined number of IDs must not exceed 200.
* The cancel request uses a single `CancelOrder` signature and returns one result per cancellation attempt. A cancellation request being accepted does not necessarily mean every target order was canceled: each result should be inspected individually. For example, a cancel target may return `Ok`, `NotFound`, `AlreadyCanceled`, `AlreadyFilled`, `AlreadyExpired`, or `NonceAlreadyUsed`.

### Batch Orders

{% hint style="warning" %}
Batch orders are currently an ***experimental feature live on testnet***, not yet available on mainnet, and remain under active development and testing.
{% endhint %}

Batch orders are currently an experimental feature live on testnet, not yet available on mainnet, and remain under active development and testing.

Use batch orders when you need to place and cancel multiple orders for the same subaccount in one request. They are best suited to workflows such as quote replacement, cancel-and-replace, portfolio rebalancing, or strategies that need to submit several related order actions with fewer round trips.

`POST /v1/order/batch`

A batch contains 1 to 20 instructions. Each instruction is one of:

* `NEW`: place a market or limit order.
* `CANCEL`: cancel one order by `orderId` or `clientOrderId`.

All instructions must resolve to the same subaccount, and each instruction has its own signature.

Before a batch reaches the engine, Ethereal validates the batch envelope and every instruction. If this preflight validation fails, the whole batch is rejected with an HTTP error and there is no `results` array to reconcile. This can happen if the batch is empty, contains more than 20 instructions, targets multiple subaccounts, includes an invalid signature, references an invalid product, includes malformed cancel instructions, etc.

Ethereal also rejects the whole batch during preflight if the request contains duplicate values that would make the result ambiguous. For example, two `NEW` instructions cannot reuse the same `clientOrderId`, two `CANCEL` instructions cannot target the same `orderId` or `clientOrderId`, two `NEW` instructions cannot reuse the same nonce, and two `CANCEL` instructions cannot reuse the same nonce.

{% hint style="info" %}
A `NEW` instruction and a `CANCEL` instruction may use the same numeric nonce because they sign different EIP-712 message types.
{% endhint %}

After preflight validation succeeds, Ethereal sends the ordered instruction list to the engine. Batch responses preserve request order: `results[0]` corresponds to `instructions[0]`. This is the main rule your integration should use when reconciling a batch response back to local state.

A successful HTTP `200` means the batch envelope was accepted, not that every instruction succeeded. Ethereal processes batch instructions with `continue-on-failure` behavior, so one failed instruction does not necessarily stop later instructions. For example, one `NEW` instruction can return `NEW_FAILED` for insufficient balance or duplicate client order ID while a later valid `NEW` instruction returns `NEW`.

The response uses different result types to distinguish per-instruction outcomes:

* `NEW`: order accepted.
* `NEW_REJECTED`: order was created but rejected during execution.
* `NEW_FAILED`: order was not created.
* `CANCEL`: cancellation was processed, with a result such as `Ok`, `NotFound`, `AlreadyCanceled`, `AlreadyFilled`, `AlreadyExpired`, or `NonceAlreadyUsed`.

There is an important distinction between a new order failing and a new order being rejected. `NEW_FAILED` means the order was not created. `NEW_REJECTED` means the order was created but rejected during execution, such as an unfilled IOC/FOK order or a post-only order that would immediately match. Both are normal per-instruction outcomes that can appear inside a successful batch response.

Cancel instructions are also per-target outcomes. A `CANCEL` result with `NotFound`, `AlreadyCanceled`, `AlreadyFilled`, `AlreadyExpired`, or `NonceAlreadyUsed` means the cancel instruction was processed and the target could not be canceled for that reason. It does not imply the whole batch failed.

Client order IDs are useful in batch workflows, but they must still be managed carefully. You can place a new order with a `clientOrderId` and later cancel by that same `clientOrderId` in the same batch. You cannot submit two new orders with the same `clientOrderId` in the same batch, and you cannot include two cancel instructions targeting the same `clientOrderId`.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ethereal.trade/developer-guides/trading-api/order-placement.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
