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 single most common mistake: chainId is not in the EIP-712 domain.
Most EIP-712 tooling (ethers, viem, eth_account) injects chainId into the domain separator by default. Reya does not put chainId in the domain. The chain id instead travels as a signed message field called verifyingChainId. If you leave chainId in your domain, your digest will diverge from what Reya reconstructs and every signature will be rejected — with no hint as to why.
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 uint256The 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:
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
OrderDetailsfield appears at its post-modify value; omission never inherits from the resting order. The modifiable set is class-split: for aLIMITorder it islimitPrice,quantity,postOnly, andexpiresAfter(thetriggerPxJSON field is omitted and the signer encodestriggerPrice = 0); for aSTOP_LOSS/TAKE_PROFITit is onlylimitPriceandtriggerPrice— the signature restates the full-position sentinelquantity(same sign),postOnly = false, the original TIF, and the originalexpiresAfter(GTT timestamp or0for GTC/IOC). Note only the magnitude of aLIMITquantityis 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 ofquantity), andsignerare restated at the resting order's values — a mismatch is rejected withINPUT_VALIDATION_ERROR.timeInForceis immutable — you cannot flip GTC ↔ GTT via modify.Targeting. Identify the resting order by
orderIdwhen present, otherwise by a non-zeroclientOrderId. The sameclientOrderIdfield restates the resting order's immutable client id for signing. If both identifiers are supplied,orderIdis canonical for lookup andclientOrderIdis the restated immutable. If the resting order has no client id, omitclientOrderId; do not send a placeholder value. Supplying neither is rejected withINPUT_VALIDATION_ERROR.Fresh nonce. Each modify carries a new, strictly-increasing
noncelike any other signed action (see Nonces). The order keeps itsorderIdandclientOrderIdacross the modify.
Cancel an order — the OrderCancel envelope
Note the integer widths. The cancel and mass-cancel envelopes use uint64 for verifyingChainId, deadline, and the detail fields — whereas the Order envelope uses uint256 for verifyingChainId/deadline and uint128/uint256 inside OrderDetails. The type string is part of the EIP-712 type hash, so using the wrong width changes the digest and the signature will be rejected.
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.
The client-id field name differs by envelope. It is clientOrderId inside OrderDetails (create/modify) but clOrdId inside OrderCancelDetails (cancel). The EIP-712 type hash is computed over the exact member name, so use each envelope's name verbatim — signing the cancel envelope with a clientOrderId member (or vice versa) changes the digest and the signature is rejected.
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.
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).
Same integer widths as the cancel envelopes. CancelAllAfter uses uint64 throughout (like OrderCancel / MassCancel), not the uint256 / uint128 widths of the Order envelope — the type string is part of the type hash, so the width must match exactly.
deadline vs expiresAfter
These are two independent time fields and integrators routinely confuse them:
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:
deadlineis required on every signed action — create, modify, cancel, mass-cancel, and cancel-all-after. The reference SDK defaults it tonow + 60s. For IOC orders and all cancels, Reya rejects adeadlinemore than 600 seconds in the future (ORDER_DEADLINE_TOO_HIGH_ERROR). GTC / GTT creates are not capped this way.expiresAftercarries a lifetime only for GTT orders, where it is required and must be strictly greater thandeadline. 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).
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:
Take a microsecond timestamp as the base:
int(time.time() * 1_000_000).Advance past any nonce already issued for that wallet:
new_nonce = max(microsecond_time, last_nonce + 1).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