> For the complete documentation index, see [llms.txt](https://docs.reya.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.reya.xyz/developers/devnet/authentication/signatures-and-nonces.md).

# 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 [pinned Python SDK onboarding snapshot](https://github.com/Reya-Labs/reya-python-sdk/tree/37450ccb2babc99398d1ac1290d48860a9a2e2fa) 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.

{% hint style="danger" %}
**The single most common mistake: `chainId` is&#x20;*****not*****&#x20;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.
{% endhint %}

## The EIP-712 domain

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

```json
{
  "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](/developers/devnet/getting-started/environments.md) 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 (e.g. `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](/developers/devnet/reference/market-ids.md).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `exchangeId`                        | The exchange id signed into the order. On devnet1 this is `1`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `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)](/developers/devnet/order-entry/trigger-orders.md#signing-the-full-position-sentinel). |
| `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](/developers/devnet/order-entry/order-types-and-tif.md).                                                                                                                                                                                                                                                                                                                        |
| `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](/developers/devnet/order-entry/order-types-and-tif.md).                                                                                                                                                                                                                                                                                                                                      |
| `signer`                            | The address of the wallet whose key produces this signature. It must be permissioned to trade for `accountId` (see [Signer Authorization](/developers/devnet/getting-started/signer-authorization.md)).                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `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](#nonces)). The order keeps its `orderId` and `clientOrderId` across the modify.

{% hint style="info" %}
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.
{% endhint %}

## Cancel an order — the `OrderCancel` envelope

```
OrderCancel:
  verifyingChainId   uint64
  deadline           uint64
  cancel             OrderCancelDetails

OrderCancelDetails:
  accountId          uint64
  marketId           uint64
  orderId            uint64
  clOrdId            uint64
  nonce              uint64
```

{% hint style="warning" %}
**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.
{% endhint %}

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.

{% hint style="warning" %}
**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.
{% endhint %}

## Cancel all — the `MassCancel` envelope

```
MassCancel:
  verifyingChainId   uint64
  deadline           uint64
  massCancel         MassCancelDetails

MassCancelDetails:
  accountId          uint64
  marketId           uint64
  nonce              uint64
```

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.

```
CancelAllAfter:
  verifyingChainId   uint64
  deadline           uint64
  cancelAllAfter     CancelAllAfterDetails

CancelAllAfterDetails:
  accountId          uint64
  timeoutMs          uint64
  nonce              uint64
```

| 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](#nonces)).                                                                                                                                                                                                       |

{% hint style="warning" %}
**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.
{% endhint %}

{% hint style="info" %}
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.
{% endhint %}

## `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)](/developers/devnet/order-entry/trigger-orders.md#choosing-the-fired-childs-time-in-force).

{% hint style="info" %}
**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](/developers/devnet/order-entry/order-types-and-tif.md).
{% endhint %}

## 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.

```python
with wallet_nonce_lock:
    current = int(time.time() * 1_000_000)
    last = wallet_nonces.get(wallet_address, 0)
    nonce = max(current, last + 1)
    wallet_nonces[wallet_address] = nonce
```

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](/developers/devnet/getting-started/environments.md); each signer then has an independent nonce sequence.

## Worked example — devnet1

The concrete values for the devnet1 perpOB testnet:

```json
// Domain
{
  "name": "Reya",
  "version": "1",
  "verifyingContract": "0x7Ec89E555c771D2B5939aBE5C4E4291852633D4D"
}

// Signed EIP-712 Order message (fields scaled to E18 where noted).
// This is not the REST/WS JSON body: optional JSON request fields are omitted
// when absent, and the signer encodes those omissions as zero here.
{
  "verifyingChainId": 89346162,
  "deadline": 1717000000,
  "order": {
    "accountId": 12345,
    "marketId": 1,                       // perp market 1 (see Market IDs)
    "exchangeId": 1,                     // devnet1
    "orderType": 0,                      // LIMIT
    "quantity": "100000000000000000",    // +0.1 (buy), E18
    "limitPrice": "65000000000000000000000", // 65000.0, E18
    "triggerPrice": 0,                    // signed no-trigger value; REST/WS LIMIT requests omit triggerPx
    "timeInForce": 1,                    // IOC
    "clientOrderId": 0,                   // signed no-tag value; REST/WS requests omit clientOrderId
    "reduceOnly": false,                 // perp IOC: required, signed as a bool
    "postOnly": false,                   // IOC can't rest, so post-only must be false
    "expiresAfter": 0,                   // signed no-expiry value; REST/WS non-GTT requests omit expiresAfter
    "signer": "0xYourSignerWalletAddress",
    "nonce": 1717000000000000
  }
}
```

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

```python
from eth_account import Account

signed = Account.sign_typed_data(private_key, domain, types, message)
signature = signed.signature.hex()  # 0x-prefixed 65-byte ECDSA signature
```

See [Environments](/developers/devnet/getting-started/environments.md) for the per-environment values, and the [pinned Python SDK onboarding snapshot](https://github.com/Reya-Labs/reya-python-sdk/tree/37450ccb2babc99398d1ac1290d48860a9a2e2fa) for the complete signing example.
