EIP-712 Signatures Guide

This guide covers all the different signature types used in the Kyan orderbook system for secure authentication and authorization.

Overview

Kyan uses EIP-712 typed data signatures for secure, structured message signing. There are 7 different signature types, each designed for specific trading operations.

EIP-712 Domain

All signatures use the same EIP-712 domain:

const EIP712Domain = {
  chainId: 421614, // Arbitrum Sepolia (use 42161 for Arbitrum One mainnet)
  name: 'Premia',
  verifyingContract: '0x...', // ClearingHouseProxy address from deployment
  version: '1',
};

Signature Types

1. UserLimitOrder

Purpose: Sign individual limit orders for both options and perpetual futures.

Type Definition:

const UserLimitOrder = [
  { name: 'deadline', type: 'uint256' },
  { name: 'instrumentName', type: 'string' },
  { name: 'size', type: 'uint256' },
  { name: 'price', type: 'uint256' },
  { name: 'taker', type: 'address' },
  { name: 'maker', type: 'address' },
  { name: 'direction', type: 'uint8' },
  { name: 'isLiquidation', type: 'bool' },
  { name: 'isPostOnly', type: 'bool' },
  { name: 'mmp', type: 'bool' },
];

Examples:

// Options Order Example
const optionsOrder = {
  instrument_name: 'BTC_USDC-31OCT25-130000-C',
  type: 'good_til_cancelled',
  contracts: 1.5,
  direction: 'buy',
  price: 1000.5,
  post_only: true,
  mmp: false,
  liquidation: false,
  maker: '0xYourAddress',
  taker: null,
};

// Perpetual Order Example
const perpsOrder = {
  instrument_name: 'BTC_USDC-PERPETUAL',
  type: 'good_til_cancelled',
  amount: 10000,
  direction: 'buy',
  price: 45000,
  post_only: false,
  mmp: false,
  liquidation: false,
  maker: '0xYourAddress',
  taker: null,
};

const deadline = Math.floor(Date.now() / 1000) + 30;

// Message structure (works for both options and perps)
const message = {
  deadline,
  instrumentName: order.instrument_name,
  size: parseUnits((order.contracts ?? order.amount).toString(), 6), // Use contracts for options, amount for perps
  price: parseUnits(order.price.toString(), 6), // 6 decimals
  taker: order.taker ?? zeroAddress,
  maker: order.maker,
  direction: order.direction === 'buy' ? 0 : 1, // Buy=0, Sell=1
  isLiquidation: order.liquidation,
  isPostOnly: order.post_only,
  mmp: order.mmp,
};

const signature = await walletClient.signTypedData({
  domain: EIP712Domain,
  types: { UserLimitOrder },
  primaryType: 'UserLimitOrder',
  message,
});

2. UserMarketOrder

Purpose: Sign market orders with limit price protection.

Type Definition:

const OrderTyped = [
  { name: 'instrumentName', type: 'string' },
  { name: 'size', type: 'uint256' },
  { name: 'direction', type: 'uint8' },
];

const UserMarketOrder = [
  { name: 'deadline', type: 'uint256' },
  { name: 'marketOrder', type: 'OrderTyped' },
  { name: 'limitPrice', type: 'uint256' },
  { name: 'taker', type: 'address' },
];

Example:

const trade = {
  market_order: {
    instrument_name: 'BTC_USDC-31OCT25-130000-C',
    contracts: 1.0,
    direction: 'buy',
  },
  limit_price: 1050.0,
  taker: '0xYourAddress',
};

const deadline = Math.floor(Date.now() / 1000) + 30;

const message = {
  deadline,
  marketOrder: {
    instrumentName: trade.market_order.instrument_name,
    size: parseUnits(trade.market_order.contracts.toString(), 6),
    direction: trade.market_order.direction === 'buy' ? 0 : 1,
  },
  limitPrice: parseUnits(trade.limit_price.toString(), 6),
  taker: trade.taker,
};

const signature = await walletClient.signTypedData({
  domain: EIP712Domain,
  types: { OrderTyped, UserMarketOrder },
  primaryType: 'UserMarketOrder',
  message,
});

3. UserComboOrder

Purpose: Sign combo trades (multiple legs executed together).

Type Definition:

const UserComboOrder = [
  { name: 'deadline', type: 'uint256' },
  { name: 'marketOrders', type: 'OrderTyped[]' },
  { name: 'limitNetPrice', type: 'int256' },
  { name: 'limitPerpPrice', type: 'int256' },
  { name: 'taker', type: 'address' },
];

Example:

const comboTrade = {
  market_orders: [
    {
      instrument_name: 'BTC_USDC-31OCT25-130000-C',
      contracts: 1.0,
      direction: 'buy',
    },
    {
      instrument_name: 'BTC_USDC-31OCT25-108000-C',
      contracts: 1.0,
      direction: 'sell',
    },
  ],
  limit_net_premium: -500.0,
  limit_perp_price: 65000.0,
  taker: '0xYourAddress',
};

const deadline = Math.floor(Date.now() / 1000) + 30;

const message = {
  deadline,
  marketOrders: comboTrade.market_orders.map((order) => ({
    instrumentName: order.instrument_name,
    size: parseUnits(order.contracts.toString(), 6),
    direction: order.direction === 'buy' ? 0 : 1,
  })),
  limitNetPrice: parseUnits(comboTrade.limit_net_premium.toString(), 6),
  limitPerpPrice: parseUnits(comboTrade.limit_perp_price.toString(), 6),
  taker: comboTrade.taker,
};

const signature = await walletClient.signTypedData({
  domain: EIP712Domain,
  types: { OrderTyped, UserComboOrder },
  primaryType: 'UserComboOrder',
  message,
});

4. CancelOrdersType

Purpose: Sign order cancellation requests for specific orders.

Type Definition:

const CancelOrdersType = [
  { name: 'deadline', type: 'uint256' },
  { name: 'maker', type: 'address' },
  { name: 'orderIds', type: 'string[]' },
];

Example:

const cancelRequest = {
  maker: '0xYourAddress',
  order_ids: ['order_123', 'order_456', 'order_789'],
};

const deadline = Math.floor(Date.now() / 1000) + 30;

const message = {
  deadline,
  maker: cancelRequest.maker,
  orderIds: cancelRequest.order_ids,
};

const signature = await walletClient.signTypedData({
  domain: EIP712Domain,
  types: { CancelOrdersType },
  primaryType: 'CancelOrdersType',
  message,
});

5. CancelAllOrdersType

Purpose: Sign cancel-all-orders requests.

Type Definition:

const CancelAllOrdersType = [
  { name: 'deadline', type: 'uint256' },
  { name: 'maker', type: 'address' },
];

Example:

const cancelAllRequest = {
  maker: '0xYourAddress',
};

const deadline = Math.floor(Date.now() / 1000) + 30;

const message = {
  deadline,
  maker: cancelAllRequest.maker,
};

const signature = await walletClient.signTypedData({
  domain: EIP712Domain,
  types: { CancelAllOrdersType },
  primaryType: 'CancelAllOrdersType',
  message,
});

6. FillRFQType

Purpose: Sign RFQ (Request for Quote) fill requests.

Type Definition:

const FillRFQType = [
  { name: 'deadline', type: 'uint256' },
  { name: 'taker', type: 'address' },
  { name: 'responseId', type: 'string' },
];

Example:

const rfqFillRequest = {
  taker: '0xYourAddress',
  response_id: 'rfq_response_123',
};

const deadline = Math.floor(Date.now() / 1000) + 30;

const message = {
  deadline,
  taker: rfqFillRequest.taker,
  responseId: rfqFillRequest.response_id,
};

const signature = await walletClient.signTypedData({
  domain: EIP712Domain,
  types: { FillRFQType },
  primaryType: 'FillRFQType',
  message,
});

7. OneClickSignature

Purpose: Create one-click trading sessions to avoid signing individual orders.

Type Definition:

const OneClickSignature = [
  { name: 'deadline', type: 'uint256' },
  { name: 'user', type: 'address' },
  { name: 'bindToIp', type: 'bool' },
];

Example:

const oneClickRequest = {
  user: '0xYourAddress',
  bind_to_ip: true,
};

const deadline = Math.floor(Date.now() / 1000) + 3600; // 1 hour session

const message = {
  deadline,
  user: oneClickRequest.user,
  bindToIp: oneClickRequest.bind_to_ip,
};

const signature = await walletClient.signTypedData({
  domain: EIP712Domain,
  types: { OneClickSignature },
  primaryType: 'OneClickSignature',
  message,
});

// Use the signature to create a session
const sessionResponse = await fetch('/api/session', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    user: oneClickRequest.user,
    bind_to_ip: oneClickRequest.bind_to_ip,
    signature_deadline: deadline,
    signature,
  }),
});

const { hash } = await sessionResponse.json();

// Now use the session hash in the x-one-click header for subsequent requests

Implementation Notes

Account Type Support

All signatures support both:

  • EOA (Externally Owned Accounts): Standard wallet signatures
  • Safe Wallets: Smart contract signature verification

Field Precision

  • Size/Amount fields: Always converted to 6 decimal precision using parseUnits(value.toString(), 6)
  • Price fields: Always converted to 6 decimal precision
  • Direction mapping: Buy = 0, Sell = 1

Critical Requirements

  • Field Order: The EIP-712 type definition field order is critical and must match exactly as shown. Changing the order will result in invalid signatures.
  • Options vs Perpetuals: Use contracts field for options orders, amount field for perpetual orders in the request payload.
  • Signature Requirements: When not using one-click sessions, both signature and signature_deadline fields are required in the request payload.

Security Considerations

  • Deadline validation: All signatures must have a deadline within 30 seconds of current time
  • Replay protection: EIP-712 domain separator prevents cross-chain replay attacks
  • Taker defaults: If no taker is specified, defaults to zeroAddress (any taker)

One-Click Sessions vs Individual Signatures

You can choose between two authentication methods:

  1. Individual Signatures: Sign each order/action separately using the appropriate signature type
  2. One-Click Sessions: Create a session once with OneClickSignature, then use the session hash in the x-one-click header for subsequent requests (no individual signatures needed)

One-click sessions are more convenient for active trading but require session management.