> For the complete documentation index, see [llms.txt](https://docs.hello.trade/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hello.trade/developer-tools/signatures.md).

# Signatures

> **⚠️ SDK Coming Soon**
>
> We are developing an official SDK that will simplify signature creation and payload encoding. The manual signature construction detailed below will remain supported but is intended primarily for advanced integrations. Stay tuned for SDK updates!

All trading operations require cryptographic signatures using Ethereum's EIP-712 (structured data) or EIP-191 (simple message) standards.

## Signature Standards

| Operation           | Standard | Struct                   | Domain |
| ------------------- | -------- | ------------------------ | ------ |
| Place/Replace Order | EIP-712  | `Order`                  | Vault  |
| Cancel Order        | EIP-712  | `OrderCancel`            | Vault  |
| Deposits (Permit)   | EIP-712  | `Permit` (ERC-2612)      | Token  |
| Deposits (Vault)    | EIP-712  | `Deposit`                | Vault  |
| Withdrawals         | EIP-712  | `Withdrawal`             | Vault  |
| Leverage Updates    | EIP-712  | `LeverageUpdate`         | Vault  |
| Margin Transfers    | EIP-712  | `MarginTransfer`         | Vault  |
| Authentication      | EIP-191  | `SimpleSignaturePayload` | N/A    |
| Mass Cancel         | EIP-191  | `SimpleSignaturePayload` | N/A    |

***

## EIP-712 Domains

EIP-712 signatures require environment-specific domain configurations. See [Getting Started - Chain Configuration](/developer-tools/getting-started.md#chain-configuration) for vault and token domain parameters.

***

## V2 Signature Format

### Payload Encoding

All payloads are now encoded with a type discriminator byte before being signed:

```
[1 byte type][abi.encode(struct)]
```

**Type Discriminators:**

* `0` = PlaceOrder (Order struct)
* `1` = OrderCancel
* `2` = SimpleSignature
* `3` = Withdrawal
* `4` = LeverageUpdate
* `5` = Permit (Deposit - Token domain)
* `6` = Liquidation (system use)
* `7` = Deposit (Deposit - Vault domain)
* `8` = MarginTransfer

**Encoding Example (TypeScript/ethers.js):**

```typescript
import { ethers } from 'ethers';

// 1. Create the struct
const order = {
  account: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  market: 8,
  size: ethers.BigNumber.from("10000000000"),
  limitPrice: ethers.BigNumber.from("500000000000000000000"),
  nonce: Date.now(),
  deadline: Math.floor(Date.now() / 1000) + 86400 * 30, // 30 days
  flags: 0 // See Flags section below
};

// 2. ABI-encode the struct
const abiCoder = new ethers.utils.AbiCoder();
const encodedStruct = abiCoder.encode(
  ['address', 'uint32', 'int256', 'uint256', 'uint256', 'uint256', 'uint256'],
  [order.account, order.market, order.size, order.limitPrice, order.nonce, order.deadline, order.flags]
);

// 3. Prepend type discriminator (0 for Order)
const payload = ethers.utils.concat([
  ethers.utils.arrayify(0), // Type byte
  encodedStruct
]);

// 4. Sign with EIP-712 (signing the original struct, not the encoded bytes)
const domain = { /* Vault domain config */ };
const types = {
  Order: [
    { name: 'account', type: 'address' },
    { name: 'market', type: 'uint32' },
    { name: 'size', type: 'int256' },
    { name: 'limitPrice', type: 'uint256' },
    { name: 'nonce', type: 'uint256' },
    { name: 'deadline', type: 'uint256' },
    { name: 'flags', type: 'uint256' }
  ]
};
const signature = await signer._signTypedData(domain, types, order);

// 5. Send to API
{
  signature: {
    sig: signature,
    payload: ethers.utils.hexlify(payload) // Encoded payload with type byte
  }
}
```

**Important:** You sign the **original struct** using EIP-712, but send the **encoded payload with type byte** in the request.

### Request Format

All signed operations use a standard wrapper format:

```typescript
{
  signature: {
    sig: string,      // Hex-encoded signature (0x-prefixed)
    payload: string   // Hex-encoded payload ([type byte][abi.encode(struct)])
  }
}
```

**Example (Order):**

```json
{
  "signature": {
    "sig": "0x1234...",
    "payload": "0x00000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f12345..."
  }
}
```

***

## Operations

### Orders

**Struct:**

```solidity
struct Order {
    address account;     // Wallet address
    uint32 market;       // Market/instrument ID (numeric)
    int256 size;         // Signed quantity (positive = buy, negative = sell)
    uint256 limitPrice;  // Limit price or market order flag
    uint256 nonce;       // Wallet nonce (milliseconds timestamp)
    uint256 deadline;    // Unix timestamp expiration (seconds)
    uint256 flags;       // Order flags (see Flags section)
}
```

**Field Specifications:**

* **`account`**: Wallet address
* **`market`**: Numeric instrument ID (e.g., 8 for NVDA)
* **`size`**: Order quantity with sign indicating direction
  * **Managed SL/TP orders** (`closePosition: true`): Always `0` - server manages quantity based on position
  * **Unmanaged SL/TP orders** (`closePosition: false`): Signed quantity (positive for Buy, negative for Sell)
  * **Regular orders**: Signed quantity (positive for Buy, negative for Sell)
  * Scaled by instrument's `quantity_precision`
* **`limitPrice`**:
  * **All order types** sign their execution-bound limit price in 18 decimals — including `StopLoss` and `TakeProfit`, which sign the `limitPrice` from the request (the trigger stays in `stopPrice` and is not part of the signature).
  * A signed `limitPrice` of `0` or `U256::MAX` (an unbounded order) is **rejected at placement**.
* **`nonce`**: Millisecond timestamp (e.g., `Date.now()`)
* **`deadline`**: Unix timestamp in **seconds** (not milliseconds). Recommended: 30+ days for resting orders
* **`flags`**: Encoded flags (see Flags section below)

#### Order Flags

The `flags` field encodes order metadata as a U256 bitmask:

**Bit Layout:**

* **Bit 0**: Margin mode (`0` = Cross, `1` = Isolated). Must match the request's `marginMode`; isolated orders draw on the market's isolated margin.
* **Bit 1**: Reduce-only (`0` = false, `1` = true)
* **Bit 2**: Managed SL/TP (`0` = unmanaged, `1` = managed)
* **Bits 3-7**: Order type (5 bits = 32 types max)
  * `0` = Limit
  * `3` = StopLimit
  * `7` = StopLoss
  * `8` = TakeProfit
  * All other values are reserved for internal use and rejected at placement.

**Encoding Example:**

```typescript
function encodeOrderFlags(options: {
  marginMode: 'Cross' | 'Isolated',
  reduceOnly: boolean,
  managed: boolean,
  orderType: 'Limit' | 'StopLimit' | 'StopLoss' | 'TakeProfit'
}): bigint {
  const orderTypeMap = {
    'Limit': 0, 'StopLimit': 3, 'StopLoss': 7, 'TakeProfit': 8
  };

  let flags = 0n;
  if (options.marginMode === 'Isolated') flags |= (1n << 0n);
  if (options.reduceOnly) flags |= (1n << 1n);
  if (options.managed) flags |= (1n << 2n);
  flags |= BigInt(orderTypeMap[options.orderType]) << 3n;

  return flags;
}

// Example: Cross margin, not reduce-only, managed SL
const flags = encodeOrderFlags({
  marginMode: 'Cross',
  reduceOnly: false,
  managed: true,
  orderType: 'StopLoss'
});
// flags = 0b0111100 = 60
```

<details>

<summary>Examples</summary>

**Limit Buy:**

```typescript
{
  account: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  market: 8,
  size: "10000000000",
  limitPrice: "500123456789012345678",
  nonce: Date.now(),
  deadline: Math.floor(Date.now() / 1000) + 86400 * 30, // 30 days
  flags: 0 // Cross margin, not reduce-only, unmanaged, Limit order
}
```

**Immediate Sell (crossing Limit IOC):**

```typescript
{
  account: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  market: 8,
  size: "-20000000000",
  limitPrice: "495000000000000000000", // e.g. mark x 0.99 — your worst acceptable price
  nonce: Date.now(),
  deadline: Math.floor(Date.now() / 1000) + 7200, // 2 hours (1 hour minimum)
  flags: 0 // Cross margin, not reduce-only, unmanaged, Limit order
}
```

**Managed Stop-Loss (Long Position, closePosition=true):**

```typescript
{
  account: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  market: 8,
  size: "0",  // Managed: server auto-syncs quantity with position
  limitPrice: "480000000000000000000",  // Execution bound (18 decimals) — must match the request's limitPrice, not the trigger
  nonce: Date.now(),
  deadline: Math.floor(Date.now() / 1000) + 86400 * 30,
  flags: 60 // Cross margin, not reduce-only, managed=1, StopLoss order (type=7)
            // Binary: 0b0111100 = (managed=1 << 2) | (orderType=7 << 3)
}
```

**Unmanaged Stop-Loss (Long Position, closePosition=false, 10 BTC):**

```typescript
{
  account: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  market: 8,
  size: "-10000000000",  // Unmanaged: fixed quantity (negative = Sell)
  limitPrice: "480000000000000000000",  // Execution bound (18 decimals) — must match the request's limitPrice
  nonce: Date.now(),
  deadline: Math.floor(Date.now() / 1000) + 86400 * 30,
  flags: 56 // Cross margin, not reduce-only, unmanaged=0, StopLoss order (type=7)
            // Binary: 0b0111000 = (managed=0 << 2) | (orderType=7 << 3)
}
```

</details>

***

### Cancel Order

**Struct:**

```solidity
struct OrderCancel {
    address account;    // Wallet address
    uint32 market;      // Market/instrument ID
    uint64 orderId;     // Order ID to cancel
    uint256 nonce;      // Wallet nonce (milliseconds timestamp)
    uint256 deadline;   // Unix timestamp expiration (seconds)
}
```

**Field Specifications:**

* **`account`**: Wallet address
* **`market`**: Numeric instrument ID
* **`orderId`**: The identifier the signature authorizes cancellation of. Please sign the same identifier sent in the cancel request — either the exchange `order_id` or your `traderOrderId`.
* **`nonce`**: Millisecond timestamp
* **`deadline`**: Unix timestamp in seconds. Must be at least 1 hour in the future (server minimum); recommended: 2 hours

Order ownership is validated before cancellation.

<details>

<summary>Example</summary>

```typescript
{
  account: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  market: 8,
  orderId: 123456789,
  nonce: Date.now(),
  deadline: Math.floor(Date.now() / 1000) + 7200 // 2 hours (1 hour minimum)
}
```

</details>

***

### Deposits

Deposits require **two signatures**:

1. **Permit signature** (ERC-2612, Token domain) - Approves token transfer
2. **Deposit signature** (Vault domain) - Authorizes vault deposit

#### Permit (ERC-2612)

**Struct:**

```solidity
struct Permit {
    address owner;      // Wallet address
    address spender;    // Vault contract address
    uint256 value;      // Amount in token base units (6 decimals for USDC)
    uint256 nonce;      // Token contract nonce
    uint256 deadline;   // Unix timestamp expiration
}
```

**Field Specifications:**

* **`owner`**: Wallet address
* **`spender`**: Vault contract address
* **`value`**: Amount in token base units (6 decimals for USDC, e.g., "1000500000" for 1000.50)
* **`nonce`**: Token contract nonce (fetch from `tokenContract.nonces(walletAddress)`)
* **`deadline`**: Unix timestamp (seconds). Must be at least 1 hour in the future (server minimum); recommended: 2 hours

**Validation Rules:**

* `value` > 0
* `spender` must equal vault contract address
* `deadline` at least 1 hour in the future

The nonce must be fetched from the token contract, not generated as a timestamp. See [Nonce & Rate Limits](/developer-tools/nonce-and-rate-limits.md).

#### Deposit (Vault Authorization)

**Struct:**

```solidity
struct Deposit {
    address account;    // Wallet address
    uint256 amount;     // Amount in token base units (6 decimals for USDC)
    uint32 market;      // Market ID (0 for cross margin)
    uint256 flags;      // Deposit flags (margin mode)
    uint256 nonce;      // Wallet nonce (milliseconds timestamp)
    uint256 deadline;   // Unix timestamp expiration
}
```

**Field Specifications:**

* **`account`**: Wallet address
* **`amount`**: Must match `Permit.value`
* **`market`**: Instrument ID of the isolated market for isolated margin; `0` for cross margin
* **`flags`**: Margin mode flags (bit 0: `0` = Cross, `1` = Isolated)
* **`nonce`**: Millisecond timestamp (different from Permit nonce)
* **`deadline`**: Unix timestamp (seconds). Must be at least 1 hour in the future (server minimum); recommended: 2 hours

**Validation Rules:**

* `amount` must equal `Permit.value`
* `market` and `flags` bit 0 must agree with the request's `instrument`/`marginMode`: `market: 0` + bit 0 = `0` for cross, `market` = instrument ID + bit 0 = `1` for isolated

<details>

<summary>Example</summary>

```typescript
// 1. Fetch token nonce
const tokenNonce = await tokenContract.nonces(walletAddress);

// 2. Create Permit signature
const permit = {
  owner: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  spender: "0x54332b922cF8c4978522f342af6b30AC01bE6f99",
  value: "100500000", // 100.50 USDC (6 decimals)
  nonce: tokenNonce,
  deadline: Math.floor(Date.now() / 1000) + 7200 // 2 hours (1 hour minimum)
};
const permitSig = await signer._signTypedData(tokenDomain, permitTypes, permit);

// 3. Encode permit payload
const permitPayload = encodePayload(5, permit); // Type 5 = Permit

// 4. Create Deposit signature
const deposit = {
  account: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  amount: "100500000", // Must match permit.value (6 decimals)
  market: 0, // Cross margin
  flags: 0, // Cross margin (bit 0 = 0)
  nonce: Date.now(),
  deadline: Math.floor(Date.now() / 1000) + 7200 // 2 hours (1 hour minimum)
};
const depositSig = await signer._signTypedData(vaultDomain, depositTypes, deposit);

// 5. Encode deposit payload
const depositPayload = encodePayload(7, deposit); // Type 7 = Deposit

// 6. Send both signatures to API
{
  amount: "100.50",
  permitSignature: {
    sig: permitSig,
    payload: permitPayload
  },
  depositSignature: {
    sig: depositSig,
    payload: depositPayload
  }
}
```

</details>

***

### Withdrawals

**Struct:**

```solidity
struct Withdrawal {
    address owner;      // Wallet address
    uint256 amount;     // Amount in token base units (6 decimals for USDC)
    uint32 market;      // Market ID (0 for cross margin)
    uint256 flags;      // Withdrawal flags (margin mode)
    uint256 nonce;      // Wallet nonce (milliseconds timestamp)
    uint256 deadline;   // Unix timestamp expiration
}
```

**Field Specifications:**

* **`owner`**: Wallet address (note: `owner`, not `account`)
* **`amount`**: Amount in token base units (6 decimals for USDC, e.g., "500250000" for 500.25)
* **`market`**: Instrument ID of the isolated market for isolated margin; `0` for cross margin
* **`flags`**: Margin mode flags (bit 0: `0` = Cross, `1` = Isolated)
* **`nonce`**: Millisecond timestamp
* **`deadline`**: Unix timestamp (seconds). Must be at least 1 hour in the future (server minimum); recommended: 2 hours

**Validation Rules:**

* `amount` > 0
* `market` and `flags` bit 0 must agree with the request's `instrument`/`marginMode`: `market: 0` + bit 0 = `0` for cross, `market` = instrument ID + bit 0 = `1` for isolated
* `deadline` at least 1 hour in the future

<details>

<summary>Example</summary>

```typescript
{
  owner: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  amount: "50250000", // 50.25 USDC (6 decimals)
  market: 0, // Cross margin
  flags: 0, // Cross margin
  nonce: Date.now(),
  deadline: Math.floor(Date.now() / 1000) + 7200 // 2 hours (1 hour minimum)
}
```

</details>

***

### Leverage Updates

**Struct:**

```solidity
struct LeverageUpdate {
    address account;    // Wallet address
    uint32 market;      // Market/instrument ID
    uint32 leverage;    // Leverage × 100
    uint256 flags;      // Leverage flags (margin mode)
    uint256 nonce;      // Wallet nonce (milliseconds timestamp)
    uint256 deadline;   // Unix timestamp expiration
}
```

**Field Specifications:**

* **`account`**: Wallet address
* **`market`**: Numeric instrument ID
* **`leverage`**: Desired leverage × 100
* **`flags`**: Margin mode flags (bit 0: `0` = Cross, `1` = Isolated) — selects which mode's leverage to update
* **`nonce`**: Millisecond timestamp
* **`deadline`**: Unix timestamp (seconds). Must be at least 1 hour in the future (server minimum); recommended: 2 hours

**Leverage Encoding:**

| Desired Leverage | `leverage` Value |
| ---------------- | ---------------- |
| 1x               | 100              |
| 5x               | 500              |
| 10x              | 1000             |
| 20x              | 2000             |
| 100x             | 10000            |

**Validation Rules:**

* `leverage` ≥ 100
* `leverage` ≤ `instrument.max_leverage × 100`
* `market` must be valid instrument ID
* `flags` bit 0 must match the request's `marginMode`
* `deadline` at least 1 hour in the future

<details>

<summary>Example</summary>

```typescript
{
  account: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  market: 8,
  leverage: 1000, // 10x
  flags: 0, // Cross margin
  nonce: Date.now(),
  deadline: Math.floor(Date.now() / 1000) + 7200 // 2 hours (1 hour minimum)
}
```

</details>

***

### Margin Transfers

Move collateral between cross margin and a market's isolated margin. Signed over the Vault domain, payload type `0x08`.

**Struct:**

```solidity
struct MarginTransfer {
    address owner;      // Wallet address
    uint256 amount;     // Amount in token base units (6 decimals for USDC)
    uint32 market;      // Instrument ID of the isolated market
    uint256 flags;      // Transfer flags (source margin mode)
    uint256 nonce;      // Wallet nonce (milliseconds timestamp)
    uint256 deadline;   // Unix timestamp expiration
}
```

**Field Specifications:**

* **`owner`**: Wallet address
* **`amount`**: Amount in token base units (6 decimals for USDC)
* **`market`**: Instrument ID of the isolated market being funded or drained (both directions)
* **`flags`**: Bit 0 carries the **source** account's margin mode — the account being debited: `0` for cross → isolated, `1` for isolated → cross. Must agree with the request's `direction`.
* **`nonce`**: Millisecond timestamp
* **`deadline`**: Unix timestamp (seconds). Must be at least 1 hour in the future (server minimum); recommended: 2 hours

**Validation Rules:**

* `amount` > 0
* `market` must match the request's `instrument`
* `flags` bit 0 must match the source side of the request's `direction`
* `deadline` at least 1 hour in the future

<details>

<summary>Example</summary>

```typescript
// Move 250 USDC from cross into BTC isolated margin
const transfer = {
  owner: "0x742d35Cc6634C0532925a3b844Bc9e7595f12345",
  amount: "250000000", // 250 USDC (6 decimals)
  market: 4, // BTC — the isolated market's instrument ID
  flags: 0, // Source is cross (bit 0 = 0); use 1 for isolated -> cross
  nonce: Date.now(),
  deadline: Math.floor(Date.now() / 1000) + 7200 // 2 hours (1 hour minimum)
};
const transferSig = await signer._signTypedData(vaultDomain, marginTransferTypes, transfer);
const transferPayload = encodePayload(8, transfer); // Type 8 = MarginTransfer

// Send to API
{
  amount: "250.00",
  instrument: "BTC",
  direction: "crossToIsolated",
  signature: {
    sig: transferSig,
    payload: transferPayload
  }
}
```

</details>

***

## EIP-191 Operations

EIP-191 uses simple personal message signing (`personal_sign`).

### Message Format

```solidity
struct SimpleSignaturePayload {
    address account;    // Wallet address
    uint256 nonce;      // Wallet nonce (milliseconds timestamp)
    uint256 deadline;   // Unix timestamp expiration (seconds)
}
```

Message construction:

```
{account}:{nonce}
```

Example:

```
0x742d35Cc6634C0532925a3b844Bc9e7595f12345:1705600000000
```

**Note:** The deadline is part of the struct but **not** part of the signed message string.

### Authentication

Used for WebSocket connection authentication.

<details>

<summary>Example</summary>

```typescript
const account = "0x742d35Cc6634C0532925a3b844Bc9e7595f12345";
const nonce = Date.now();
const deadline = Math.floor(Date.now() / 1000) + 300; // 5 minutes
const message = `${account}:${nonce}`;
const signature = await signer.signMessage(message);

// Encode payload
const payload = {
  account: account,
  nonce: nonce,
  deadline: deadline
};
const encodedPayload = encodePayload(2, payload); // Type 2 = SimpleSignature

{
  signature: {
    sig: signature,
    payload: encodedPayload
  }
}
```

</details>

### Mass Cancel

Cancels all orders, optionally filtered by instrument.

<details>

<summary>Example</summary>

```typescript
const account = "0x742d35Cc6634C0532925a3b844Bc9e7595f12345";
const nonce = Date.now();
const deadline = Math.floor(Date.now() / 1000) + 300;
const message = `${account}:${nonce}`;
const signature = await signer.signMessage(message);

const payload = {
  account: account,
  nonce: nonce,
  deadline: deadline
};
const encodedPayload = encodePayload(2, payload);

{
  signature: {
    sig: signature,
    payload: encodedPayload
  },
  instrument: "NVDA"
}
```

</details>

***

## Signature Validation Errors

| Error                       | Description                                                                                                                     |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `SignatureError`            | Malformed signature bytes                                                                                                       |
| `IncorrectAddress`          | Recovered signer doesn't match claimed address                                                                                  |
| `DeadlineExpired`           | Signature deadline expired                                                                                                      |
| `DeadlineTooSoon`           | Deadline below the server minimum: 1 hour for collateral ops, cancels, and IOC/FOK orders; 25 hours for DAY; 7 days for GTC/GTD |
| `InstrumentNotFound`        | Unknown instrument symbol                                                                                                       |
| `InstrumentMismatch`        | Signed market ID doesn't match requested instrument                                                                             |
| `AmountMismatch`            | Signed amount doesn't match request                                                                                             |
| `OrderSizeMismatch`         | Signed size doesn't match requested quantity                                                                                    |
| `PriceMismatch`             | Signed price doesn't match request                                                                                              |
| `OrderIdMismatch`           | Signed order ID doesn't match request                                                                                           |
| `LeverageMismatch`          | Signed leverage doesn't match request                                                                                           |
| `MarginModeMismatch`        | Signed margin mode doesn't match request                                                                                        |
| `FlagMismatch`              | Signed flags don't match request                                                                                                |
| `OrderTypeMismatch`         | Signed order type doesn't match request                                                                                         |
| `ZeroAmount`                | Amount must be greater than zero                                                                                                |
| `WrongSpender`              | Permit spender doesn't match vault address                                                                                      |
| `LeverageBelowMinimum`      | Leverage must be ≥ 100                                                                                                          |
| `LeverageExceedsMaximum`    | Leverage exceeds instrument maximum                                                                                             |
| `VaultAddressNotConfigured` | Vault address not configured                                                                                                    |
| `TokenAddressNotConfigured` | Token address not configured                                                                                                    |
| `DecodeError`               | Failed to decode payload                                                                                                        |

For complete error code reference, see [Error Codes](/developer-tools/error-codes.md).
