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 requestsImplementation 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
contractsfield for options orders,amountfield for perpetual orders in the request payload. - Signature Requirements: When not using one-click sessions, both
signatureandsignature_deadlinefields 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:
- Individual Signatures: Sign each order/action separately using the appropriate signature type
- One-Click Sessions: Create a session once with
OneClickSignature, then use the session hash in thex-one-clickheader for subsequent requests (no individual signatures needed)
One-click sessions are more convenient for active trading but require session management.
Updated 5 days ago
