> ## 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 Generate and Use a Session Key

> Authorize a DerivaDEX session key by signing a delegated-session payload with the trader wallet, encode the sessionKeySignature with an expiry and action scope, and sign orders and cancels with the session key instead of the trader wallet.

## Generate and use a session key

Use this when you want to sign requests from a key that is not your funded trader wallet, such as a trading process that should never hold the funded wallet's key. A session key is a separate wallet the trader authorizes to sign a bounded set of trading actions until a chosen expiry. The trader wallet signs that authorization once; the session key then signs each request, so the funded wallet stays out of the per-request loop.

You need the trader wallet key that controls the account, a separate session key wallet, an expiry as a Unix timestamp, and the set of actions the session key may take.

Two keys play two roles. The trader wallet authorizes the session key once by signing a delegated-session payload. The session key signs each request it is scoped for, and that authorization travels inside the request as the `sessionKeySignature`. [Signed Private Requests](/api-reference/rest/signed-requests) covers where that field sits in request content.

Use the network values you are authorizing for:

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

## Follow the session-key procedure

1. Obtain the trader wallet key material. This is the funded account that authorizes the session and that the operator already recognizes.

2. Generate a separate session key wallet. This key signs trading requests and never holds collateral.

3. Choose an expiry as a Unix timestamp in seconds. The operator rejects the session key once that time passes.

4. Choose the action scope: the actions the session key is allowed to sign. Scope codes are `0` unrestricted, `1` order, `2` modify-order, `3` cancel-order, `4` cancel-all.

5. Sign the delegated-session payload — the expiry, the action scope, and the session public key — with the trader wallet to produce the `sessionKeySignature`.

6. Include the `sessionKeySignature` in each request, and sign the request hash with the session key instead of the trader wallet.

7. Recover both signers locally before submission.

A session key authorizes only the actions in its scope, and only until its expiry. To revoke one earlier, add it to the trader's delegated-session deny list through a profile update; see [Signed Private Requests](/api-reference/rest/signed-requests).

## Worked example

This example authorizes a session key for `Order` and `CancelOrder`, then signs an order with it. The sample keys and expiry are fixed only so the result is reproducible. In production, load your own trader and session wallets and set the expiry relative to the current time.

```python theme={null}
import time
from enum import IntEnum

from eth_account import Account

# Sample key material for this worked example only. In production, load the
# funded trader wallet and a separate session key wallet from your own store.
trader_private_key = "0x9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501"
session_private_key = "0x6f1e3c1d2b9a8f7e6d5c4b3a29180706f5e4d3c2b1a09f8e7d6c5b4a39281706"

trader_account = Account.from_key(trader_private_key)
session_account = Account.from_key(session_private_key)
trader_address_with_prefix = f"0x00{trader_account.address[2:]}"

# In production: session_expiry = int(time.time()) + 60 * 60  # one hour out
session_expiry = 1770878619  # fixed here so the example is reproducible


class SessionActionNames(IntEnum):
    UNRESTRICTED = 0
    ORDER = 1
    MODIFY_ORDER = 2
    CANCEL_ORDER = 3
    CANCEL_ALL = 4


acl_scope = [SessionActionNames.ORDER, SessionActionNames.CANCEL_ORDER]
```

### Example: authorize the session key

Authorizing session keys can be implemented in two ways here:

* `Direct implementation` builds and signs the delegated-session payload directly.
* `DDX client library` shows the shortest path if you already use `ddx-python`.

Both approaches produce the same `sessionKeySignature` for the same input.

#### Direct implementation

The domain helpers are the same ones used in [How to Sign DerivaDEX Requests with EIP-712](/how-to-guides/sign-requests-with-eip712). The session-key payload adds its own struct hash on top:

```python theme={null}
from collections import OrderedDict
import cbor2
from dataclasses import dataclass

from eth_abi import encode
from eth_utils.crypto import keccak

EIP191_HEADER = b"\x19\x01"


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()


@dataclass
class SessionKeyPayload:
    expiry: int
    acl_scope: list[int]
    session_public_key: bytes


def compute_message_struct_hash(session_key_payload: SessionKeyPayload) -> bytes:
    session_key_payload_type_hash = keccak(
        b"SessionDelegatedPayload(uint256 expiry,uint256[] aclScope,bytes sessionPublicKey)"
    )
    acl_hash = keccak(
        b"".join(encode(["uint256"], [scope]) for scope in session_key_payload.acl_scope)
    )
    session_public_key_hash = keccak(session_key_payload.session_public_key)

    return keccak(
        session_key_payload_type_hash
        + encode(["uint256"], [session_key_payload.expiry])
        + acl_hash
        + session_public_key_hash
    )
```

Assemble the payload, sign it with the trader wallet, and encode the result. The encoded `aclScope` carries the action names, not the numeric codes:

```python theme={null}
acl_names = [scope.name.title().replace("_", "") for scope in acl_scope]

session_key_payload = SessionKeyPayload(
    expiry=session_expiry,
    acl_scope=acl_scope,
    session_public_key=session_account._key_obj.public_key.to_compressed_bytes(),
)

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

session_key_payload_signature = trader_account.unsafe_sign_hash(eip712_hash)

payload = OrderedDict(
    [
        ("sessionSignature", session_key_payload_signature.signature),
        ("expiry", session_key_payload.expiry),
        ("aclScope", acl_names),
        ("sessionPublicKey", session_key_payload.session_public_key),
    ]
)
session_key_signature = "0x" + (b"\x02" + cbor2.dumps(payload, canonical=False)).hex()
```

For this input, the target result is:

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

#### DDX client library

If you already use `ddx-python`, one call signs and encodes the same payload and returns the session signer address alongside it:

```python theme={null}
from ddx._rust.common import make_session_key_signature
# Only run the following two load_testnet() lines if using testnet; can omit otherwise
from ddx import load_testnet
load_testnet()

session_key_signature, session_signer_address = make_session_key_signature(
    trader_private_key,
    session_private_key,
    session_expiry,
    acl_scope,
)
```

`session_key_signature` matches the direct result above, and `session_signer_address` matches `session_account.address`.

### Use the session key in a request

Build the request as you would in [How to Sign DerivaDEX Requests with EIP-712](/how-to-guides/sign-requests-with-eip712), with two changes: set `session_key_signature` on the request, and sign the request hash with the session key rather than the trader wallet.

```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
from ddx.rest_client.clients.signed_client import sign_eip712_hash

order_intent = OrderIntent(
    symbol="ETHP",
    strategy="main",
    side=OrderSide.Bid,
    order_type=OrderType.Limit,
    amount=Decimal("0.1"),
    price=Decimal("1800"),
    stop_price=Decimal("0"),
    nonce="0x3137373038373530313938323238333436363300000000000000000000000000",
    client_timestamp_ms=1770875019823,
    recv_window_ms=5000,
    session_key_signature=session_key_signature,
)
order_hash = order_intent.hash_eip712()
order_intent.signature = sign_eip712_hash(session_account, order_hash)
```

The order hash is the same `OrderParams` hash from the EIP-712 signing guide, because the `sessionKeySignature` rides in the request content rather than being folded into `OrderParams`. For these order fields that hash is `0x8d5950c5691e5c4054c537f8a7b14e33dee9222f34e98fe8120624d636e37ba5`.

For live submission with a session key tied to a funded trader wallet, hand the session account to the client as the local signer:

```python theme={null}
# receipt = await client.signed.place_order(
#     order_intent,
#     local_account=session_account,
# )
```

## Check your result before you submit

1. Recover the request signer from the order signature and confirm it is the session key, not the trader wallet.
2. Recover the trader signer from the `sessionKeySignature` payload and confirm it is the trader wallet that authorized the session.
3. Confirm the requested action is within the session key's scope and that the expiry is still in the future.

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

recovered_session_address = Account._recover_hash(
    bytes.fromhex(order_hash.removeprefix("0x")),
    signature=bytes.fromhex(order_intent.signature.removeprefix("0x")),
)
recovered_order_hash, recovered_trader_address = order_intent.recover_signer()

assert recovered_session_address.lower() == session_account.address.lower()
assert recovered_trader_address.lower() == trader_address_with_prefix.lower()

print(f"Session signer:   {session_account.address}")
print(f"Recovered session:{recovered_session_address}")
print(f"Trader wallet:    {trader_address_with_prefix}")
print(f"Recovered trader: {recovered_trader_address}")
```

A decode failure, an expired payload, or an action outside the scope returns `SignedRequestAuthenticationFailed`. A denied session signer returns `Forbidden`. See [Error Reference](/reference-public/error-reference) for the exact rejection path.

## Appendix

The `sessionKeySignature` is a CBOR-encoded map with a one-byte version prefix:

| Part               | What it contains                                                                                     |
| ------------------ | ---------------------------------------------------------------------------------------------------- |
| `sessionSignature` | Trader wallet's EIP-712 signature over `SessionDelegatedPayload(expiry, aclScope, sessionPublicKey)` |
| `expiry`           | Unix timestamp in seconds after which the session key is rejected                                    |
| `aclScope`         | Action names the session key may sign, such as `Order` and `CancelOrder`                             |
| `sessionPublicKey` | Compressed secp256k1 public key of the session wallet                                                |
| Final value        | `0x02` version byte followed by the CBOR-encoded payload, hex-encoded with a `0x` prefix             |

## Continue with submission

Once the session key has signed the request hash and the `sessionKeySignature` is in the request content, encrypt and submit the request the same way as any signed request. Use [How to Encrypt Requests for the Operator](/how-to-guides/encrypt-operator-requests) for encryption and submission to `POST /v2/request`.

If the operator rejects a session-key request after local recovery matches, use [Error Reference](/reference-public/error-reference) for the failure code.
