> ## Documentation Index
> Fetch the complete documentation index at: https://docs.derivadex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Sign DerivaDEX Requests with EIP-712

> Sign DerivaDEX order, cancel, cancel-all, profile-update, and withdraw-intent requests in Python by following the exact EIP-712 hashing steps DerivaDEX expects.

## Sign DerivaDEX requests with EIP-712

Use this when you need a clear implementation of the signing flow DerivaDEX expects. The examples use Python because the procedure is easiest to read there, but the same steps apply in any language that can build the same bytes and hashes.

You need the `chainId`, the DerivaDEX verifying smart contract address, the exact request you plan to send, and the wallet key that controls the trader account.

Use the network values you are signing for:

| Network           |  `chainId` | DerivaDEX verifying smart contract address   |
| ----------------- | ---------: | -------------------------------------------- |
| Testnet (Sepolia) | `11155111` | `0x5d1a3b4181d3cad422f404f28e9e972d0ba4dad6` |
| Mainnet           |        `1` | `0x6fb8aa6fc6f27e591423009194529ae126660027` |

After the signature is correct, move to [How to Encrypt Requests for the Operator](/how-to-guides/encrypt-operator-requests).

## Follow the signing procedure

1. Define the EIP-191 prefix.

```python theme={null}
EIP191_HEADER = b"\x19\x01"
```

2. Build the EIP-712 domain separator from the `chainId` and the DerivaDEX verifying smart contract address.

   Use the same domain values for every request:

   | Field               | Value                                                                |
   | ------------------- | -------------------------------------------------------------------- |
   | `name`              | `DerivaDEX`                                                          |
   | `version`           | `1`                                                                  |
   | `chainId`           | The Ethereum chain ID for the DerivaDEX network you are signing for  |
   | `verifyingContract` | The DerivaDEX verifying smart contract address for that same network |

3. Choose the request type you are signing, then build its message hash with the exact field order DerivaDEX expects.

   | Request                           | Struct signature      | Fields, in order                                                                                                                                                                      |
   | --------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   | Order                             | `OrderParams`         | `symbol`, `strategy`, `side`, `orderType`, `nonce`, `clientTimestampMs`, `recvWindowMs`, `amount`, `price`, `stopPrice`                                                               |
   | Modify one order                  | `ModifyOrderParams`   | `orderHash`, `symbol`, `strategy`, `side`, `orderType`, `nonce`, `clientTimestampMs`, `recvWindowMs`, `amount`, `price`, `stopPrice`                                                  |
   | Cancel one order                  | `CancelOrderParams`   | `symbol`, `orderHash`, `nonce`, `clientTimestampMs`, `recvWindowMs`                                                                                                                   |
   | Cancel all orders in one strategy | `CancelAllParams`     | `symbol`, `strategy`, `nonce`, `clientTimestampMs`, `recvWindowMs`                                                                                                                    |
   | Update profile                    | `UpdateProfileParams` | `payFeesInDdxState`, `referralAddress`, `deniedDelegatedSessionAction`, `deniedDelegatedSessionAddress`, `deniedDelegatedSessionExpiry`, `nonce`, `clientTimestampMs`, `recvWindowMs` |
   | Withdraw collateral               | `WithdrawParams`      | `strategyId`, `currency`, `amount`, `nonce`, `clientTimestampMs`, `recvWindowMs`                                                                                                      |
   | Withdraw DDX                      | `WithdrawDDXParams`   | `amount`, `nonce`, `clientTimestampMs`, `recvWindowMs`                                                                                                                                |

4. Compute the final digest as `keccak256(EIP191_HEADER + domain_separator + message_struct_hash)`.

5. Sign that digest with the trader wallet.

6. Recover the signer locally before submission and confirm that it matches the trader wallet you expect.

## Worked examples

Start with the helper functions that stay the same across request types.

Each request type can be implemented in two ways here:

* `Direct implementation` shows the hashing steps directly.
* `DDX client library` shows the shortest path if you already use the library.

Both approaches should produce the same EIP-712 hash for the same input.

```python theme={null}
from decimal import ROUND_DOWN, Decimal

from eth_abi import encode
from eth_utils.crypto import keccak

EIP191_HEADER = b"\x19\x01"
TOKEN_UNIT_SCALE = 6


def encode_string_as_bytes32(value: str) -> bytes:
    raw = value.encode("utf-8")
    if len(raw) > 31:
        raise ValueError("DerivaDEX string fields must fit in 31 bytes")
    return bytes([len(raw)]) + raw + bytes(31 - len(raw))


def scale_decimal(value: Decimal) -> int:
    fixed = value.quantize(Decimal("0.000001"), rounding=ROUND_DOWN)
    return int(fixed * (10 ** TOKEN_UNIT_SCALE))


def compute_eip712_domain_separator(chain_id: int, verifying_contract: str) -> bytes:
    domain_type_hash = keccak(
        b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
    )
    return keccak(
        domain_type_hash
        + keccak(b"DerivaDEX")
        + keccak(b"1")
        + encode(["uint256"], [chain_id])
        + encode(["address"], [verifying_contract])
    )


def compute_eip712_hash(
    chain_id: int,
    verifying_contract: str,
    message_struct_hash: bytes,
) -> str:
    domain_separator = compute_eip712_domain_separator(chain_id, verifying_contract)
    digest = keccak(EIP191_HEADER + domain_separator + message_struct_hash)
    return "0x" + digest.hex()
```

### Example: sign an order request

This example follows the steps above for `OrderParams`.

#### Direct implementation

```python theme={null}
from dataclasses import dataclass
from enum import IntEnum


class OrderSide(IntEnum):
    BID = 0
    ASK = 1


class OrderType(IntEnum):
    LIMIT = 0
    MARKET = 1
    STOP = 2
    LIMIT_POST_ONLY = 3


@dataclass
class OrderIntent:
    symbol: str
    strategy: str
    side: OrderSide
    order_type: OrderType
    nonce: str
    clientTimestampMs: int
    recvWindowMs: int
    amount: Decimal
    price: Decimal
    stop_price: Decimal


def compute_message_struct_hash(intent: OrderIntent) -> bytes:
    order_type_hash = keccak(
        b"OrderParams(bytes32 symbol,bytes32 strategy,uint256 side,uint256 orderType,bytes32 nonce,uint256 clientTimestampMs,uint256 recvWindowMs,uint256 amount,uint256 price,uint256 stopPrice)"
    )
    return keccak(
        order_type_hash
        + encode(["bytes32"], [encode_string_as_bytes32(intent.symbol)])
        + encode(["bytes32"], [encode_string_as_bytes32(intent.strategy)])
        + encode(["uint256"], [intent.side])
        + encode(["uint256"], [intent.order_type])
        + encode(["bytes32"], [bytes.fromhex(intent.nonce.removeprefix("0x"))])
        + encode(["uint256"], [intent.clientTimestampMs])
        + encode(["uint256"], [intent.recvWindowMs])
        + encode(["uint256"], [scale_decimal(intent.amount)])
        + encode(["uint256"], [scale_decimal(intent.price)])
        + encode(["uint256"], [scale_decimal(intent.stop_price)])
    )
```

Example input:

```python theme={null}
intent = OrderIntent(
    symbol="ETHP",
    strategy="main",
    side=OrderSide.BID,
    order_type=OrderType.LIMIT,
    nonce="0x3137373038373530313938323238333436363300000000000000000000000000",
    clientTimestampMs=1770875019823,
    recvWindowMs=5000,
    amount=Decimal("0.1"),
    price=Decimal("1800"),
    stop_price=Decimal("0"),
)

message_struct_hash = compute_message_struct_hash(intent)
eip712_hash = compute_eip712_hash(
    chain_id=11155111,
    verifying_contract="0x5d1a3b4181d3cad422f404f28e9e972d0ba4dad6",
    message_struct_hash=message_struct_hash,
)
```

For this input, the target result is:

```python theme={null}
"0x8d5950c5691e5c4054c537f8a7b14e33dee9222f34e98fe8120624d636e37ba5"
```

#### DDX client library

If you already use `ddx-python`, this is the shortest path through the same procedure:

```python theme={null}
from ddx._rust.common.enums import OrderSide, OrderType
from ddx._rust.common.requests.intents import OrderIntent
from ddx._rust.decimal import Decimal

chain_id = 11155111
verifying_contract = "0x5d1a3b4181d3cad422f404f28e9e972d0ba4dad6"

order_intent = OrderIntent(
    "ETHP",
    "main",
    OrderSide.Bid,
    OrderType.Limit,
    "0x3137373038373530313938323238333436363300000000000000000000000000",
    1770875019823,
    5000,
    Decimal("0.1"),
    Decimal("1800"),
    Decimal("0"),
    None,
)

eip712_hash = order_intent.hash_eip712((chain_id, verifying_contract))

print(f"Order hash: {eip712_hash}")
```

For this input, the DDX client library should return the same result as the native example above:

```python theme={null}
"0x8d5950c5691e5c4054c537f8a7b14e33dee9222f34e98fe8120624d636e37ba5"
```

### Sign the hash and recover the signer

Once the EIP-712 hash is correct, signing it is the short final step. This example uses a generated wallet only to show the mechanics:

```python theme={null}
from eth_account import Account

account = Account.create()
digest_bytes = bytes.fromhex(eip712_hash.removeprefix("0x"))

signed = account.signHash(digest_bytes)
signature = signed.signature.hex()

recovered_address = Account._recover_hash(digest_bytes, signature=signed.signature)

print(f"Address:   {account.address}")
print(f"Signature: {signature}")
print(f"Recovered: {recovered_address}")
```

The recovered address should match the wallet address that signed the hash. In production, use the trader wallet that is meant to authorize the request, not a generated example wallet.

Use [Signed Private Requests](/api-reference/rest/signed-requests) for the field tables behind cancel, cancel-all, profile-update, and withdraw request content.

## Check your result before you submit

1. Recover the signing address from the signature and digest.
2. Confirm that it matches the trader wallet you expect to authorize the request.

If the recovered address is wrong, stop there. The operator will reject a signature that does not authorize the submitted request with `SignedRequestAuthenticationFailed`.

## Appendix

Use this section when you need the exact field rules behind the steps and examples.

| Field family                       | Rule                                                                                                                          |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `symbol`, `strategy`, `strategyId` | Encode as `bytes32` with one length byte, then UTF-8 bytes, then zero padding                                                 |
| `nonce`                            | Keep it as a 32-byte hex value. Generate a fresh nonce for each new intent, but do not treat nonce order as replay freshness. |
| `clientTimestampMs`                | Use `serverTimeMs` from the same operator's `GET /v2/time` response before signing                                            |
| `recvWindowMs`                     | Sign the receive window in milliseconds. Client helpers default to `5000`; the operator rejects `0` and values above `60000`. |
| Decimal values                     | Scale to fixed-point integers at `10^6`, cutting off anything past six decimal places first                                   |
| Order side                         | `Bid = 0`, `Ask = 1`                                                                                                          |
| Order type                         | `Limit = 0`, `Market = 1`, `Stop = 2`, `LimitPostOnly = 3`                                                                    |
| Withdraw `currency`                | Sign the ERC-20 token address directly                                                                                        |

Profile updates need three extra rules:

| Field                          | Rule                                         |
| ------------------------------ | -------------------------------------------- |
| `payFeesInDdxState`            | `false = 0`, `true = 1`                      |
| Empty `referralAddress`        | Use zero-filled `bytes32`                    |
| Empty delegated-session update | Use action `0`, zero address, and expiry `0` |

## Continue with submission

After the signature is correct, fetch the operator key, encrypt the signed JSON, and submit the bytes to `POST /v2/request`. Use [How to Encrypt Requests for the Operator](/how-to-guides/encrypt-operator-requests) for that step.

If the request still fails after local recovery matches the trader wallet, use [Error Reference](/reference-public/error-reference) for the exact rejection path.
