Place orders using the create -> sign -> submit pattern described in Action Pattern.
The examples on this page use Hyperliquid (provider: 'hyperliquid'). The trading API is DEX-agnostic — replace the provider value with any supported DEX from getProviders().
Order Flow
createAction → actions[] → sign each typedData → executeAction → results[]
USER_AGENT mode (automatic signing)
In USER_AGENT mode, the PerpsClient handles the entire flow:
const result = await perps.placeOrder({
address: userAddress,
provider: 'hyperliquid',
asset: { assetId: 'BTC', market: 'hyperliquid' },
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 { createAction, executeAction } from '@lifi/perps-sdk';
// Step 1: Create order → get actions to sign
const { actions } = await createAction(client, {
provider: 'hyperliquid',
address: userAddress,
action: 'placeOrder',
params: {
asset: { assetId: 'BTC', market: 'hyperliquid' },
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 executeAction(client, {
provider: 'hyperliquid',
address: userAddress,
action: 'placeOrder',
actions: signedActions,
});
Or use PerpsClient.submitSignedOrder():
const result = await perps.submitSignedOrder({
provider: '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,
provider: 'hyperliquid',
asset: { assetId: 'BTC', market: 'hyperliquid' },
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,
provider: 'hyperliquid',
asset: { assetId: 'ETH', market: 'hyperliquid' },
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,
provider: 'hyperliquid',
asset: { assetId: 'BTC', market: 'hyperliquid' },
side: 'BUY',
type: 'LIMIT',
size: '0.1',
price: '90000.00',
timeInForce: 'GTT',
expiresAt: '1779123456789', // Unix ms — must be in the future
});
API Reference: POST /actions