Skip to main content
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

ValueDescription
GTCGood til cancelled (default)
IOCImmediate or cancel — unfilled portion is cancelled
POST_ONLYCancelled if it would immediately match (maker only)
GTTGood 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:
#ActionDescription
1updateLeverageSet leverage to 10 (if different from current)
2placeOrderBUY 0.1 BTC @ 95500.00
3placeTriggerOrderTake profit at 100000.00 (market)
4placeTriggerOrderStop 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:
TriggerOrderTypeDescription
TAKE_PROFITTriggers when price moves in your favor
STOP_LOSSTriggers when price moves against you
TriggerOrderStatusDescription
WAITINGTrigger condition not yet met
TRIGGEREDCondition met, order executed
CANCELLEDTrigger 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

StatusDescription
PENDINGSubmitted, not yet on orderbook
OPENResting on orderbook
PARTIALLY_FILLEDSome quantity filled
FILLEDFully filled
CANCELLEDCancelled by user
REJECTEDRejected by DEX
EXPIREDGTT order expired
TRIGGEREDTrigger 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

CodeNameCause
2021InsufficientMarginNot enough margin for order size/leverage
2022InsufficientBalanceAccount lacks funds
2020ExchangeRejectedDEX rejected (invalid params, market closed)
2024OrderNotFoundOrder ID doesn’t exist or already filled
2042NonceExpiredPayload 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);
FieldTypeRequiredDescription
addressstringYesUser’s wallet address
dexstringYesDEX identifier
symbolstringYesTrading symbol
side'BUY' | 'SELL'YesOrder direction
type'MARKET' | 'LIMIT'YesOrder type
sizestringYesOrder size
pricestringYesLimit price or slippage limit
leveragenumberNoLeverage to set
reduceOnlybooleanNoOnly reduce position
timeInForcestringNoGTC, IOC, POST_ONLY, GTT
expiresAtstringNoISO 8601 expiry for GTT
takeProfitTriggerOrderInputNoTake profit trigger
stopLossTriggerOrderInputNoStop 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);
FieldTypeRequiredDescription
addressstringYesUser’s wallet address
dexstringYesDEX identifier
idsstring[]YesOrder 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);
FieldTypeRequiredDescription
dexstringYesDEX identifier
addressstringYesUser’s wallet address
actionsSignedOrderAction[]YesSigned actions from createOrder/cancelOrder
Each SignedOrderAction:
FieldTypeRequiredDescription
actionstringYesAction type from the create response
typedDataobjectYesOriginal typedData from createOrder/cancelOrder
signaturestringYesUser’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' }]