# Signing Method A — ActionHash

Method A is used by **every write endpoint except the four account-relationship ones** (Agent approve / revoke / renew, sub-account create). Method A wraps a hash of the business parameters in a shared EIP-712 `Agent` struct.

> If you are signing **a trade or a leverage change**, you are using Method A.

## EIP-712 Domain (Shared By Both Methods)

```json
{
  "name": "UniX",
  "version": "1",
  "chainId": 1
}
```

`EIP712Domain(string name,string version,uint256 chainId)` — no `verifyingContract`.

## The `Agent` Struct

`target_address` does not enter the `actionHash` canonical JSON, but when the request carries a `target_address`, it must be included in the outer EIP-712 `Agent` struct — this prevents a signed action from being replayed against a different target account.

**Without `target_address` — 5-field struct:**

```
Agent(address sender, bytes32 actionHash, uint64 nonce, uint64 expiresAfter)
```

**With `target_address` — 6-field struct:**

```
Agent(address sender, address targetAddress, bytes32 actionHash, uint64 nonce, uint64 expiresAfter)
```

| Field           | Type      | Source                                                                                   |
| --------------- | --------- | ---------------------------------------------------------------------------------------- |
| `sender`        | `address` | The signer's own Ethereum address (matches `address` in the HTTP body).                  |
| `targetAddress` | `address` | Present only when the HTTP request carries `target_address`; equals that target account. |
| `actionHash`    | `bytes32` | `keccak256(tag_byte ‖ canonical_json(business_params))` — see below.                     |
| `nonce`         | `uint64`  | Millisecond Unix timestamp.                                                              |
| `expiresAfter`  | `uint64`  | Expiration timestamp (ms). Recommended `now + 600_000`.                                  |

## Computing `actionHash`

Two inputs: the **action tag** (a `uint8` identifying the operation) and the **canonical JSON** of the business parameters.

```
actionHash = keccak256( uint8(tag) ‖ canonical_json_bytes )
```

### Canonical JSON Rules

| Rule            | Detail                                                                                                                                                    |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Serialization   | Standard JSON, identical to `JSON.stringify` / `serde_json::to_vec`.                                                                                      |
| Key ordering    | **Alphabetical** (`a` → `z`). Object keys must be emitted lexicographically.                                                                              |
| Format          | Compact — **no whitespace, no newlines, no indentation**.                                                                                                 |
| Optional fields | Fields that are `null` / unset are **omitted entirely** from the JSON.                                                                                    |
| Numbers         | Integers emitted as bare numbers (no quotes). Decimal values are strings (see [Conventions](file:///1548985/conventions/request-and-response-format.md)). |
| Booleans        | `true` / `false`.                                                                                                                                         |
| Scope           | **Every business field is signed.** There are no "ignored" fields.                                                                                        |

**Example.** A limit-buy order without optional fields:

```json
{"is_buy":true,"order_type":"limit","position_side":"both","price":"67500.00","quantity":"1.0","symbol_id":100001}
```

> Keys are alphabetical. Optional fields (`algo_order_type`, `flags`, `tpsl`, …) are not present because they were not supplied. **Don't include them as `null`** — leave them out entirely.

### Action Tag Table

| Tag | Action          | Endpoint                              |
| :-: | --------------- | ------------------------------------- |
|  2  | Deposit         | `POST /v1/account/deposit` (internal) |
|  7  | PlaceOrder      | `POST /v1/trade/orders`               |
|  8  | CancelOrder     | `POST /v1/trade/orders/cancel`        |
|  9  | CancelAll       | `POST /v1/trade/orders/cancel-all`    |
|  10 | SetPositionMode | `POST /v1/account/position-mode`      |
|  11 | SetLeverage     | `POST /v1/account/leverage`           |
|  12 | ModifyOrder     | `POST /v1/trade/orders/modify`        |
|  13 | ChaseOrder      | `POST /v1/trade/orders/chase`         |
|  15 | UpdateMargin    | `POST /v1/account/isolated-margin`    |
|  16 | BatchCancel     | `POST /v1/trade/orders/batch/cancel`  |
|  17 | BatchOrder      | `POST /v1/trade/orders/batch`         |
|  18 | BatchModify     | `POST /v1/trade/orders/batch/modify`  |

See [Action Tags](file:///1548985/reference/action-tags.md) for the full table including deprecated tags.

> Tags `20`–`25` are **deprecated** — their operations now use [Signing Method B](broken://pages/1b294f6690c8101dac23707ef1ffe599bc1d8479) (Typed Data direct sign) instead of an action hash. Once a tag is published it is never reused or repurposed.

## Computing `signing_hash`

```
domainSeparator = keccak256(
    keccak256("EIP712Domain(string name,string version,uint256 chainId)")
    ‖ keccak256("UniX")
    ‖ keccak256("1")
    ‖ pad32(chainId)
)

# Without target_address — 5-field Agent:
structHash = keccak256(
    keccak256("Agent(address sender,bytes32 actionHash,uint64 nonce,uint64 expiresAfter)")
    ‖ pad32(sender)        // address, left-padded to 32 bytes
    ‖ actionHash           // bytes32
    ‖ pad32(nonce)         // uint64, big-endian, right-aligned in 32 bytes
    ‖ pad32(expiresAfter)  // uint64, big-endian, right-aligned in 32 bytes
)

# With target_address — 6-field Agent:
structHash = keccak256(
    keccak256("Agent(address sender,address targetAddress,bytes32 actionHash,uint64 nonce,uint64 expiresAfter)")
    ‖ pad32(sender)
    ‖ pad32(targetAddress)
    ‖ actionHash
    ‖ pad32(nonce)
    ‖ pad32(expiresAfter)
)

signing_hash = keccak256( "\x19\x01" ‖ domainSeparator ‖ structHash )
```

> `signing_hash` is both the input to ECDSA signing **and** the on-chain `tx_hash`. You can compute it client-side and know your transaction's hash before submission.

## Full Example — Place A Limit Order

{% stepper %}
{% step %}

### 1. Business parameters

```python
action = {
    "is_buy": True,
    "margin_mode": "cross",
    "order_type": "limit",
    "position_side": "both",
    "price": "67500.00",
    "quantity": "1.0",
    "symbol_id": 100001,
    "time_in_force": "gtc",
}
```

{% endstep %}

{% step %}

### 2. canonical JSON → actionHash (tag = 7 for PlaceOrder)

```python
canonical = json.dumps(action, sort_keys=True, separators=(",", ":")).encode()
action_hash = Web3.keccak(bytes([7]) + canonical)
```

{% endstep %}

{% step %}

### 3. Sign Agent struct

```python
domain = {"name": "UniX", "version": "1", "chainId": 1}
types = {"Agent": [
    {"name": "sender", "type": "address"},
    {"name": "actionHash", "type": "bytes32"},
    {"name": "nonce", "type": "uint64"},
    {"name": "expiresAfter", "type": "uint64"},
]}
nonce = int(time.time() * 1000)
message = {
    "sender": "0xYOUR_ADDRESS",
    "actionHash": action_hash,
    "nonce": nonce,
    "expiresAfter": nonce + 600_000,
}
signed = Account.sign_message(
    encode_typed_data(domain, types, "Agent", message),
    private_key="0xYOUR_PRIVATE_KEY",
)
```

{% endstep %}

{% step %}

### 4. POST

```python
resp = requests.post("http://10.34.8.77:8481/v1/trade/orders", json={
    **action,
    "address": "0xYOUR_ADDRESS",
    "nonce": nonce,
    "expires_after": nonce + 600_000,
    "signature": {"r": hex(signed.r), "s": hex(signed.s), "v": signed.v},
})
print(resp.json())
```

> **The `address` field is required in the body** and must equal the recovered signer from `signature`. The node verifies both — if they differ, the request is rejected with error `10001`.
> {% endstep %}
> {% endstepper %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.lynxtrade.world/authentication/signing-method-a-actionhash.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
