For the complete documentation index, see llms.txt. This page is also available as Markdown.

Signatures & Nonces

Every order-management action on Reya — create, modify, cancel, cancel-all, cancel-all-after — is authorised by an EIP-712 signature from your trading wallet. Reya verifies signatures, signer permissions, order validity and nonces, and performs pre-trade risk checks. Use the exact envelopes, encoding rules and nonce sequence below so your requests pass validation.

The Python SDK 3.6.1.0 contains the reference signing example — its sdk/reya_rest_api/auth/signatures.py produces every envelope below. If you can, sign through the SDK; if you're porting to another language, match it field-for-field.

The EIP-712 domain

The domain is shared by all four envelopes (Order, OrderCancel, MassCancel, CancelAllAfter) and has exactly three fields:

{
  "name": "Reya",
  "version": "1",
  "verifyingContract": "<OrdersGateway proxy address for your environment>"
}

No chainId. No salt. The verifyingContract is the OrdersGateway proxy for the environment you're trading on — see Environments for the per-environment value.

Create an order — the Order envelope

Order:
  verifyingChainId   uint256
  deadline           uint256
  order              OrderDetails

OrderDetails:
  accountId          uint128
  marketId           uint128
  exchangeId         uint128
  orderType          uint8
  quantity           int256
  limitPrice         uint256
  triggerPrice       uint256
  timeInForce        uint8
  clientOrderId      uint64
  reduceOnly         bool
  postOnly           bool
  expiresAfter       uint256
  signer             address
  nonce              uint256

The struct member names above are the signed EIP-712 names. On the REST/WS wire the JSON fields use the short forms: signed OrderDetails.triggerPrice/limitPrice are sent as JSON triggerPx/limitPx (and signed quantity splits into isBuy + qty).

Encoding rules — get these exactly right:

Field
Rule

verifyingChainId

The chain id of your environment (1729 on mainnet, 89346162 on devnet1). A message field, not a domain field.

marketId

The market's id, as returned by the market-definition endpoints — sign it exactly as given. See Market IDs.

exchangeId

The exchange id signed into the order. This is 1 on mainnet and devnet1.

orderType

0 = LIMIT, 1 = STOP_LOSS, 2 = TAKE_PROFIT.

quantity

Signed int256. For a LIMIT order: the size scaled to E18 (value × 10^18), the sign encoding direction (positive = buy, negative = sell) — don't pass an unsigned magnitude with a separate side flag into the signature; the sign is the side. For a STOP_LOSS / TAKE_PROFIT: the full-position sentinel ±(2²⁵⁵ − 1) (type(int256).max), a raw integer — not E18-scaled — whose sign carries the close side to match isBuy (JSON qty is omitted entirely). Any other trigger quantity, including 0, is rejected. See Trigger Orders (SL/TP).

limitPrice, triggerPrice

uint256, scaled to E18. For a non-trigger order, triggerPrice is 0; for STOP_LOSS / TAKE_PROFIT it is required non-zero.

timeInForce

0 = GTC, 1 = IOC, 2 = GTT. GTT (good-til-time) additionally requires a non-zero expiresAfter that is strictly greater than deadline (see below). Every create, including STOP_LOSS / TAKE_PROFIT, requires the JSON field and signs the selected value. A trigger chooses its fired child's TIF. Trigger modifies restate that same TIF and original GTT expiry; GTC/IOC sign expiresAfter = 0.

clientOrderId

REST/WS clients omit the JSON field when they do not want a client tag; the EIP-712 OrderDetails.clientOrderId value for that omission is 0.

reduceOnly

Signed as a bool. Perp IOC only — required on perp IOC creates; not supported on perp GTC / GTT, spot, or TP/SL creates. Omit it from unsupported creates and sign false. A trigger modify sends reduceOnly: false as an immutable full restate. See Order Types & Time-in-Force.

postOnly

Signed as a bool. Maker-only intent: valid on GTC / GTT and rejected on IOC (which can never rest). A TP/SL create may omit it or send false; true is rejected. A trigger modify sends postOnly: false as an immutable full restate. See Order Types & Time-in-Force.

signer

The address of the wallet whose key produces this signature. It must be permissioned to trade for accountId (see Signer Authorization).

expiresAfter, deadline, nonce

See the sections below.

Modify an order — reuses the Order envelope

modifyOrder does not have its own signing envelope. An in-place amendment of a resting LIMIT order (GTC or GTT) or armed trigger is signed with the same Order / OrderDetails EIP-712 envelope as createOrder, over the complete post-modify state. The JSON request carries that complete signed state plus the target id, but its field set is class-specific: in particular, a trigger modify requires immutable timeInForce, reduceOnly, and postOnly values, while a trigger create requires timeInForce, omits reduceOnly, and may omit a false postOnly. The signature is verified over exactly the state represented by the fields you send; omitted values are not inherited from the resting order.

  • Restate every value. Every OrderDetails field appears at its post-modify value; omission never inherits from the resting order. The modifiable set is class-split: for a LIMIT order it is limitPrice, quantity, postOnly, and expiresAfter (the triggerPx JSON field is omitted and the signer encodes triggerPrice = 0); for a STOP_LOSS / TAKE_PROFIT it is only limitPrice and triggerPrice — the signature restates the full-position sentinel quantity (same sign), postOnly = false, the original TIF, and the original expiresAfter (GTT timestamp or 0 for GTC/IOC). Note only the magnitude of a LIMIT quantity is modifiable — its sign encodes the side, which is immutable and must match the resting order.

  • Immutables must match the resting order. accountId, marketId, exchangeId, orderType, timeInForce, reduceOnly, clientOrderId, side (the sign of quantity), and signer are restated at the resting order's values — a mismatch is rejected with INPUT_VALIDATION_ERROR. timeInForce is immutable — you cannot flip GTC ↔ GTT via modify.

  • Targeting. Identify the resting order by orderId when present, otherwise by a non-zero clientOrderId. The same clientOrderId field restates the resting order's immutable client id for signing. If both identifiers are supplied, orderId is canonical for lookup and clientOrderId is the restated immutable. If the resting order has no client id, omit clientOrderId; do not send a placeholder value. Supplying neither is rejected with INPUT_VALIDATION_ERROR.

  • Fresh nonce. Each modify carries a new, strictly-increasing nonce like any other signed action (see Nonces). The order keeps its orderId and clientOrderId across the modify.

Because the envelope is identical to createOrder, the class-specific encoding rules above apply unchanged. A LIMIT modify signs the E18 total post-modify quantity; a trigger modify restates the raw signed full-position sentinel while JSON qty remains omitted. Prices are E18. A GTT LIMIT modify is how you refresh a resting order's expiresAfter; the request must carry the new future lifetime. Omit expiresAfter for GTC/IOC. A GTT trigger modify must restate its original expiry; changing it requires cancel-and-create. A fired protective child is cancel-only.

Cancel an order — the OrderCancel envelope

For cancels, send one target: set orderId and pass clOrdId = 0, or set orderId = 0 and pass a non-zero clOrdId (client order id). The reference SDK emits exactly one target. If a generated schema version accepts both identifiers, treat that as compatibility only and do not rely on it; orderId is the canonical target. Cancel works identically for spot and perp.

Cancel all — the MassCancel envelope

Pass marketId = 0 to cancel across all markets; pass a specific marketId to scope the mass-cancel to one market.

Cancel-all-after — the CancelAllAfter envelope

cancelAllAfter arms an account-scoped dead-man's switch: while armed, the matching engine cancels every open order on the account (all markets, spot + perp) except protective stops (STOP_LOSS / TAKE_PROFIT), which the dead-man's switch deliberately leaves in place — so its scope is narrower than a marketId = 0 mass-cancel (which cancels everything). It fires unless you send another cancelAllAfter before the countdown elapses. Each call is independently signed.

Field
Rule

accountId

The account whose open orders the countdown covers.

timeoutMs

Countdown duration in milliseconds. 0 disarms; any non-zero value must be within [5000, 60000] (inclusive) — out-of-range values are rejected with INPUT_VALIDATION_ERROR. Re-sending (even the same value) replaces the countdown (last-write-wins) and is how you refresh the heartbeat.

deadline

EIP-712 signature-validity window (Unix seconds) — not the trigger time. The trigger time is timeoutMs relative to server receipt.

nonce

A fresh, strictly-increasing per-wallet nonce on every arm / refresh / disarm (see Nonces).

The switch is transport-agnostic: REST POST /v2/cancelAllAfter and the ws-exec cancelAllAfter message arm the same account-level timer (one canonical deadline per account). It is not tied to your connection — closing the WebSocket neither fires nor disarms it, and order-entry traffic and pings do not refresh it; only another cancelAllAfter does, and only the countdown elapsing fires it.

deadline vs expiresAfter

These are two independent time fields and integrators routinely confuse them:

Field
Governs
When checked

deadline

Signature validity — how long this signed request may be accepted for entry

At order entry

expiresAfter

Order lifetime — when a resting GTT order auto-expires

While the order is active and at settlement

Both are Unix timestamps in seconds and both are always part of the signed payload. They differ in when each is required:

  • deadline is required on every signed action — create, modify, cancel, mass-cancel, and cancel-all-after. The reference SDK defaults it to now + 60s. For IOC orders and all cancels, Reya rejects a deadline more than 600 seconds in the future (ORDER_DEADLINE_TOO_HIGH_ERROR). GTC / GTT creates are not capped this way.

  • expiresAfter carries a lifetime only for GTT orders, where it is required and must be strictly greater than deadline. For GTC and IOC, including triggers with those TIFs, omit it. A GTT trigger carries the same expiry through arming and firing; both phases end early to allow time for settlement. See Trigger Orders (SL/TP).

GTT is the only time-in-force that carries a lifetime. A GTT (good-til-time) LIMIT order rests until its expiresAfter, then it is automatically cancelled with a CANCELLED order update. The settlement contract also rejects any fill that lands on-chain after the signed expiresAfter. GTC rests indefinitely; IOC never rests. So expiresAfter is meaningful only when timeInForce = 2 (GTT); omit it for everything else. See Order Types & Time-in-Force.

Nonces

Each signed action carries a per-wallet nonce to prevent replay. The rules:

  • Unique — never reuse a nonce for a given wallet.

  • Strictly increasing — each new action's nonce must exceed the previous one for that wallet.

  • Per wallet — nonces are scoped to the signing wallet, not per market or per account.

  • Transport-agnostic — REST and WebSocket Order Entry actions signed by the same wallet consume the same nonce sequence. A separate connection or transport does not create a separate sequence.

Strategy for parallel submission

Integrators that sign and submit many orders concurrently need nonces that are both unique and monotonic under races. The reference SDK's approach, which you should mirror:

  1. Take a microsecond timestamp as the base: int(time.time() * 1_000_000).

  2. Advance past any nonce already issued for that wallet: new_nonce = max(microsecond_time, last_nonce + 1).

  3. Do this under a per-wallet lock so concurrent signers share one strictly-increasing counter.

The microsecond base keeps nonces naturally increasing across restarts, while max(…, last + 1) guarantees that two orders signed in the same microsecond — or faster than the clock ticks — still get distinct, ordered nonces. If you run multiple signing processes for one wallet, they must coordinate on a shared counter; independent per-process clocks can collide.

Monotonic allocation alone is not enough if requests for one signer can arrive out of order. Coordinate dispatch across every REST client and WebSocket connection using that signer. For independently concurrent submission without a shared dispatch queue, authorise a pool of signer wallets through the environment's signer-management page; each signer then has an independent nonce sequence.

Worked example — mainnet

The concrete values for Reya mainnet:

Sign it with eth_account (which derives the EIP712Domain type from the domain keys, so the absent chainId stays absent):

See Environments for the per-environment values, and the Python SDK 3.6.1.0 for the complete signing example.

Transport failures and retrying

See Handling ambiguous outcomes for the API 3.6.2 error codes and when to retry or reconcile. An unanswered request may have been applied.

Last updated