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:
Fetch the subaccount you will trade from.
Fetch product metadata from
GET /v1/product.Build the order payload using the product’s
onchainId,engineType,tickSize,lotSize, min/max price, and max quantity.Generate a fresh
nonceandsignedAttimestamp.Sign the EIP-712 order message.
Submit the order.
Track the order by
idor your ownclientOrderId.Cancel, replace, or batch-update orders as needed.
For detailed information about order mechanics and advanced order types, refer to the trading documentation.
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.
NEW
A submitted order that has successfully submitted, acknowledged and visible on the books.
PENDING
An acknowledged order that has not yet been triggered.
FILLED_PARTIAL
A partially filled order.
FILLED
A closed order that has been fully filled.
CANCELED
A canceled order that may have been partially filled or not. You can identify which state by consuming the filled property. A non-zero filled value and canceled status is the former whereas a zero filled is the latter.
EXPIRED
An order becomes expired when the current system time reaches or exceeds the expireTime timestamp that was assigned during submission, automatically removing it from the order book.
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.
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 use0for PERP.side:0for buy,1for sell.quantity: decimal string in native product units (e.g."1.15").price: required for limit orders (0if 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.
Decimal fields support up to 9 decimal places. Send decimal values as strings so precision is not lost before the payload is signed.
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:
quantitymust be greater than zero unlesscloseis true.quantitymust be at leastlotSize.quantitymust be a multiple oflotSize.quantitymust not exceedmaxQuantity.Limit order
pricemust be positive.priceandstopPricemust be multiples oftickSize.pricemust be betweenminPriceandmaxPrice.
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 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:
sendersubaccountquantitypricereduceOnlysideengineTypeproductIdnoncesignedAt
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:
sendersubaccountnonce
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.
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
noncefor each signed order or cancel request.Send
nonceas a string, not a JSON number.Use seconds for
signedAtand nanoseconds fornonce.If provided,
expiresAtmust be aftersignedAt.expiresAtmust be in the future.expiresAtmust be no more than6,652,800seconds aftersignedAt.
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
201with the assigned orderid, optionalclientOrderId, 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
filledfield in the placement response is deprecated (see Block Execution). 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
CancelOrdersignature 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 returnOk,NotFound,AlreadyCanceled,AlreadyFilled,AlreadyExpired, orNonceAlreadyUsed.
Batch Orders
Batch orders are currently an experimental feature live on testnet, not yet available on mainnet, and remain under active development and testing.
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 byorderIdorclientOrderId.
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.
A NEW instruction and a CANCEL instruction may use the same numeric nonce because they sign different EIP-712 message types.
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 asOk,NotFound,AlreadyCanceled,AlreadyFilled,AlreadyExpired, orNonceAlreadyUsed.
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.
Last updated