Place orders, cancel orders, and query order status. All trading operations follow the create → sign → submit pattern described in Action Pattern.
The examples on this page use Hyperliquid (dex: 'hyperliquid'). The trading API is DEX-agnostic — replace the dex value with any supported DEX from getDexes().
Order Flow
createOrder / cancelOrder → actions[] → sign each typedData → submitOrder → results[]
Placing Orders
USER_AGENT mode (automatic signing)
In USER_AGENT mode, the PerpsClient handles the entire flow:
const result = await perps.placeOrder({
address: userAddress,
dex: 'hyperliquid',
symbol: 'BTC',
side: 'BUY',
type: 'MARKET',
size: '0.1',
price: '95500.00',
leverage: 10,
});
console.log(result.results);
// [{ action: 'placeOrder', success: true, orderId: '12345678' }]
USER mode (manual signing)
In USER mode, use the service functions to control each step:
import { createOrder, submitOrder } from '@lifi/perps-sdk';
// Step 1: Create order → get actions to sign
const { actions } = await createOrder(client, {
dex: 'hyperliquid',
address: userAddress,
symbol: 'BTC',
side: 'BUY',
type: 'LIMIT',
size: '0.1',
price: '94000.00',
timeInForce: 'GTC',
});
// Step 2: Sign each action with wallet
const signedActions = await Promise.all(
actions.map(async (a) => ({
action: a.action,
typedData: a.typedData,
// Using viem walletClient:
signature: await walletClient.signTypedData({ ...a.typedData }),
// Or using a raw EIP-1193 provider:
// signature: await provider.request({
// method: 'eth_signTypedData_v4',
// params: [address, JSON.stringify(a.typedData)],
// }),
}))
);
// Step 3: Submit signed payloads
const result = await submitOrder(client, {
dex: 'hyperliquid',
address: userAddress,
actions: signedActions,
});
Or use PerpsClient.submitSignedOrder():
const result = await perps.submitSignedOrder({
dex: 'hyperliquid',
address: userAddress,
actions: signedActions,
});
Order Types
Market Order
Executes immediately at the best available price. The price field acts as a slippage limit.
await perps.placeOrder({
address: userAddress,
dex: 'hyperliquid',
symbol: 'BTC',
side: 'BUY',
type: 'MARKET',
size: '0.1',
price: '95500.00', // slippage limit
});
Limit Order
Rests on the orderbook until filled or cancelled.
await perps.placeOrder({
address: userAddress,
dex: 'hyperliquid',
symbol: 'ETH',
side: 'BUY',
type: 'LIMIT',
size: '1.0',
price: '3150.00',
timeInForce: 'GTC', // Good til cancelled
});
Time in Force options
| Value | Description |
|---|
GTC | Good til cancelled (default) |
IOC | Immediate or cancel — unfilled portion is cancelled |
POST_ONLY | Cancelled if it would immediately match (maker only) |
GTT | Good til time — requires expiresAt |
// GTT example
await perps.placeOrder({
address: userAddress,
dex: 'hyperliquid',
symbol: 'BTC',
side: 'BUY',
type: 'LIMIT',
size: '0.1',
price: '90000.00',
timeInForce: 'GTT',
expiresAt: '2025-12-31T23:59:59Z',
});
Take Profit & Stop Loss
Attach trigger orders to any order using the takeProfit and stopLoss fields:
const { actions } = await createOrder(client, {
dex: 'hyperliquid',
address: userAddress,
symbol: 'BTC',
side: 'BUY',
type: 'MARKET',
size: '0.1',
price: '95500.00',
leverage: 10,
takeProfit: {
triggerPrice: '100000.00', // Triggers when mark price >= 100000
},
stopLoss: {
triggerPrice: '90000.00', // Triggers when mark price <= 90000
limitPrice: '89800.00', // Executes as limit order at 89800
},
});
This produces multiple actions:
| # | Action | Description |
|---|
| 1 | updateLeverage | Set leverage to 10 (if different from current) |
| 2 | placeOrder | BUY 0.1 BTC @ 95500.00 |
| 3 | placeTriggerOrder | Take profit at 100000.00 (market) |
| 4 | placeTriggerOrder | Stop loss at 90000.00 (limit @ 89800.00) |
If limitPrice is omitted from a trigger order, it executes as a market order when triggered.
Trigger Order Enums
The SDK exports enums for trigger order types and their lifecycle status:
TriggerOrderType | Description |
|---|
TAKE_PROFIT | Triggers when price moves in your favor |
STOP_LOSS | Triggers when price moves against you |
TriggerOrderStatus | Description |
|---|
WAITING | Trigger condition not yet met |
TRIGGERED | Condition met, order executed |
CANCELLED | Trigger order cancelled |
Cancelling Orders
USER_AGENT mode
const result = await perps.cancelOrders({
dex: 'hyperliquid',
address: userAddress,
ids: ['12345678'],
});
USER mode (manual signing)
import { cancelOrder, submitOrder } from '@lifi/perps-sdk';
// Step 1: Build cancel payloads
const { actions } = await cancelOrder(client, {
dex: 'hyperliquid',
address: userAddress,
ids: ['12345678', '12345679'],
});
// Step 2: Sign and submit
const signedActions = await Promise.all(
actions.map(async (a) => ({
action: a.action,
typedData: a.typedData,
signature: await walletClient.signTypedData({ ...a.typedData }),
}))
);
await submitOrder(client, {
dex: 'hyperliquid',
address: userAddress,
actions: signedActions,
});
Use the orderId returned from the submit response to cancel orders.
Getting Order Status
import { getOrder } from '@lifi/perps-sdk';
const order = await getOrder(client, {
dex: 'hyperliquid',
address: userAddress,
id: '12345678',
});
console.log(order.status); // 'FILLED'
console.log(order.averagePrice); // '95050.00'
console.log(order.filledSize); // '0.1'
Order Status Values
| Status | Description |
|---|
PENDING | Submitted, not yet on orderbook |
OPEN | Resting on orderbook |
PARTIALLY_FILLED | Some quantity filled |
FILLED | Fully filled |
CANCELLED | Cancelled by user |
REJECTED | Rejected by DEX |
EXPIRED | GTT order expired |
TRIGGERED | Trigger order activated (TP/SL) |
Error Handling
Trading operations may throw PerpsError with numeric error codes. Use PerpsErrorCode for type-safe handling:
import { PerpsError, PerpsErrorCode } from '@lifi/perps-sdk';
try {
await perps.placeOrder({
address: userAddress,
dex: 'hyperliquid',
symbol: 'BTC',
side: 'BUY',
type: 'MARKET',
size: '0.1',
price: '95500.00',
});
} catch (error) {
if (error instanceof PerpsError) {
switch (error.code) {
case PerpsErrorCode.InsufficientMargin: // 2021
console.error('Not enough margin. Reduce size or add funds.');
break;
case PerpsErrorCode.InsufficientBalance: // 2022
console.error('Insufficient balance. Deposit more funds.');
break;
case PerpsErrorCode.MarketNotFound: // 2023
console.error('Invalid symbol. Check available markets.');
break;
case PerpsErrorCode.AgentUnauthorized: // 2011
console.error('Agent not authorized. Complete authorization flow.');
break;
case PerpsErrorCode.NonceExpired: // 2042
console.error('Payload expired. Retry the operation.');
break;
default:
console.error(`Error ${error.code}: ${error.message}`);
}
}
}
Common Trading Errors
| Code | Name | Cause |
|---|
| 2021 | InsufficientMargin | Not enough margin for order size/leverage |
| 2022 | InsufficientBalance | Account lacks funds |
| 2020 | ExchangeRejected | DEX rejected (invalid params, market closed) |
| 2024 | OrderNotFound | Order ID doesn’t exist or already filled |
| 2042 | NonceExpired | Payload TTL exceeded — retry operation |
See Error Codes for the complete reference.
PerpsClient Methods
placeOrder
Place an order (USER_AGENT mode only — auto-signs and submits).
const result = await perps.placeOrder(params);
| Field | Type | Required | Description |
|---|
address | string | Yes | User’s wallet address |
dex | string | Yes | DEX identifier |
symbol | string | Yes | Trading symbol |
side | 'BUY' | 'SELL' | Yes | Order direction |
type | 'MARKET' | 'LIMIT' | Yes | Order type |
size | string | Yes | Order size |
price | string | Yes | Limit price or slippage limit |
leverage | number | No | Leverage to set |
reduceOnly | boolean | No | Only reduce position |
timeInForce | string | No | GTC, IOC, POST_ONLY, GTT |
expiresAt | string | No | ISO 8601 expiry for GTT |
takeProfit | TriggerOrderInput | No | Take profit trigger |
stopLoss | TriggerOrderInput | No | Stop loss trigger |
Returns: SubmitOrderResponse with results[] array.
buildOrder
Build order payloads for signing (works in both USER and USER_AGENT mode). In USER_AGENT mode, automatically injects the agent address as signerAddress.
const { actions } = await perps.buildOrder(params);
// Sign each action with the user's wallet
const signedActions = await Promise.all(
actions.map(async (a) => ({
action: a.action,
typedData: a.typedData,
signature: await walletClient.signTypedData({ ...a.typedData }),
}))
);
await perps.submitSignedOrder({
dex: 'hyperliquid',
address: userAddress,
actions: signedActions,
});
Parameters: Same as placeOrder.
Returns: CreateOrderResponse with actions[] array.
cancelOrders
Cancel one or more orders (USER_AGENT mode only).
const result = await perps.cancelOrders(params);
| Field | Type | Required | Description |
|---|
address | string | Yes | User’s wallet address |
dex | string | Yes | DEX identifier |
ids | string[] | Yes | Order IDs to cancel |
buildCancelOrder
Build cancel order payloads for signing (works in both USER and USER_AGENT mode). In USER_AGENT mode, automatically injects the agent address as signerAddress.
const { actions } = await perps.buildCancelOrder({
dex: 'hyperliquid',
address: userAddress,
ids: ['12345678'],
});
Returns: CancelOrderPayloadResponse with actions[] array.
submitSignedOrder
Submit pre-signed order payloads (USER mode).
const result = await perps.submitSignedOrder(params);
| Field | Type | Required | Description |
|---|
dex | string | Yes | DEX identifier |
address | string | Yes | User’s wallet address |
actions | SignedOrderAction[] | Yes | Signed actions from createOrder/cancelOrder |
Each SignedOrderAction:
| Field | Type | Required | Description |
|---|
action | string | Yes | Action type from the create response |
typedData | object | Yes | Original typedData from createOrder/cancelOrder |
signature | string | Yes | User’s or agent’s signature of the typedData |
API Reference: POST /createOrder, POST /cancelOrder, POST /order, GET /order/
End-to-End Example
A complete example: wallet setup, authorization, and placing an order using USER_AGENT mode.
import { PerpsClient, getDexes, getMarkets } from '@lifi/perps-sdk';
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { arbitrum } from 'viem/chains';
// --- Wallet setup (viem with private key) ---
const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY');
const walletClient = createWalletClient({
account,
chain: arbitrum,
transport: http(),
});
const userAddress = account.address;
// --- Wallet setup (wagmi — browser) ---
// import { useWalletClient, useAccount } from 'wagmi';
//
// const { data: walletClient } = useWalletClient();
// const { address: userAddress } = useAccount();
// 1. Create the client
const perps = new PerpsClient({
integrator: 'my-app',
apiKey: 'your-api-key',
});
// 2. Fetch available DEXes and markets
const { dexes } = await getDexes(perps.client);
console.log(dexes.map((d) => d.name)); // ['Hyperliquid', ...]
const { markets } = await getMarkets(perps.client, { dex: 'hyperliquid' });
console.log(markets.map((m) => m.symbol)); // ['BTC', 'ETH', 'SOL', ...]
// 3. Set up agent signing
await perps.setSigningMode(userAddress, 'hyperliquid', 'USER_AGENT');
// 4. Authorize (one-time) — checks which approvals are needed
const required = await perps.getRequiredAuthorizations({
dex: 'hyperliquid',
address: userAddress,
});
if (!required.isReady) {
// Sign only the actions that need user wallet approval
const signedActions = await Promise.all(
required.userAuthorizations.map(async (a) => ({
action: a.action,
typedData: a.typedData,
signature: await walletClient.signTypedData({ ...a.typedData }),
}))
);
// Submit user-signed actions + auto-sign agent actions
await perps.executeAuthorizations({
dex: 'hyperliquid',
address: userAddress,
required,
userSignedActions: signedActions,
});
}
// 5. Place an order (agent signs automatically — no wallet popup)
const result = await perps.placeOrder({
address: userAddress,
dex: 'hyperliquid',
symbol: 'BTC',
side: 'BUY',
type: 'MARKET',
size: '0.1',
price: '95500.00',
leverage: 10,
});
console.log(result.results);
// [{ action: 'placeOrder', success: true, orderId: '12345678' }]