# Account API Source: https://public-perps-docs.mintlify.app/api-reference/account Endpoints for account information, positions, and order history Endpoints for fetching account information, positions, and order history. *** ## GET /account Get account summary including balances, positions, open orders, and fee tier. ``` GET /v1/perps/account?dex=hyperliquid&address=0x1234... ``` ### Parameters | Name | In | Type | Required | Description | | -------------------------------- | ------ | --------------------- | -------- | --------------------- | | `dex` | query | `string` | Yes | DEX identifier | | `address` | query | `string` | Yes | User's wallet address | | `x-lifi-api-key` | header | `string` | Yes | API key | | `x-lifi-integrator` | header | `string` | No | Integrator identifier | ### Response `200` ```json theme={null} { "dex": "hyperliquid", "address": "0x1234567890abcdef1234567890abcdef12345678", "balances": [ { "currency": "USDC", "amount": "10000.00" }, { "currency": "ETH", "amount": "2.5" } ], "marginUsed": "2500.00", "unrealizedPnl": "150.00", "feeTier": { "maker": "0.0002", "taker": "0.0005" }, "positions": [ { "symbol": "BTC", "assetId": 0, "dex": "hyperliquid", "side": "LONG", "size": "0.5", "entryPrice": "94000.00", "markPrice": "95000.50", "liquidationPrice": "85000.00", "unrealizedPnl": "500.25", "leverage": 10, "marginUsed": "4700.00", "marginMode": "ISOLATED" } ], "openOrders": [ { "id": "12345678", "symbol": "ETH", "assetId": 1, "dex": "hyperliquid", "side": "BUY", "type": "LIMIT", "size": "1.0", "price": "3150.00", "filledSize": "0", "reduceOnly": false, "createdAt": "2025-01-15T10:30:00Z" } ], "config": { "abstractionStatus": "unifiedAccount", "agents": [], "builderFeeApproval": { "builderAddress": "0x5678901234abcdef5678901234abcdef56789012", "maxFeeRate": "0.001", "approved": true } } } ``` **SDK:** [`getAccount()`](/sdk/account#getaccount) *** ## GET /history Get paginated order history. Results are sorted by creation time, newest first. ``` GET /v1/perps/history?dex=hyperliquid&address=0x1234...&limit=50 ``` ### Parameters | Name | In | Type | Required | Description | | -------------------------------- | ------ | ---------------------- | -------- | ----------------------------------------- | | `dex` | query | `string` | Yes | DEX identifier | | `address` | query | `string` | Yes | User's wallet address | | `startTime` | query | `integer` | No | Filter: orders after this timestamp (ms) | | `endTime` | query | `integer` | No | Filter: orders before this timestamp (ms) | | `cursor` | query | `string` | No | Pagination cursor from previous response | | `limit` | query | `integer` | No | Items per page (default 50, max 100) | | `x-lifi-api-key` | header | `string` | Yes | API key | | `x-lifi-integrator` | header | `string` | No | Integrator identifier | ### Response `200` The `filledSize`, `fee`, and `realizedPnl` fields are optional -- they may be absent on cancelled or rejected orders, and `realizedPnl` is `null` when the order did not close a position. ```json theme={null} { "dex": "hyperliquid", "items": [ { "id": "12345678", "symbol": "BTC", "assetId": 0, "dex": "hyperliquid", "side": "BUY", "type": "MARKET", "size": "0.5", "price": "94000.00", "status": "FILLED", "filledSize": "0.5", "fee": "4.70", "realizedPnl": "125.50", "createdAt": "2025-01-15T09:00:00Z" } ], "pagination": { "limit": 50, "hasMore": true, "cursor": "abc123", "nextUrl": "/v1/perps/history?address=0x1234...&cursor=abc123&limit=50" } } ``` ### Pagination Use the `cursor` from the response to fetch the next page: ``` GET /v1/perps/history?dex=hyperliquid&address=0x1234...&cursor=abc123 ``` When `hasMore` is `false`, there are no more pages. **SDK:** [`getHistory()`](/sdk/account#gethistory) # Authorization API Source: https://public-perps-docs.mintlify.app/api-reference/authorization Endpoints for building and submitting delegate authorizations Endpoints for building and submitting delegate authorizations (agent wallets, builder fees, etc.). These endpoints are intended to be used via [`@lifi/perps-sdk`](https://www.npmjs.com/package/@lifi/perps-sdk). The typed data signing and submission flow is complex and best handled by the SDK. *** ## POST /createAuthorization Build authorization payloads for the user to sign. ``` POST /v1/perps/createAuthorization ``` ### Flow 1. Call `GET /dexes` to discover available authorizations and their parameters 2. Call this endpoint with the desired authorizations array 3. Backend checks the current state and returns only unsatisfied authorizations 4. Sign the `typedData` in each returned action with the user's wallet 5. Submit all signatures to `POST /authorization` ### Request Body ```json theme={null} { "dex": "hyperliquid", "address": "0x1234567890abcdef1234567890abcdef12345678", "authorizations": [ { "key": "ApproveAgent", "params": { "agentAddress": "0x5678901234abcdef5678901234abcdef56789012" } }, { "key": "ApproveBuilderFee", "params": {} } ] } ``` | Field | Type | Required | Description | | ----------------------------- | ----------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------ | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address (account owner) | | `signerAddress` | `string` | No | Address that will sign the payloads. Required for `USER_AGENT` mode (set to agent address). Defaults to `address`. | | `authorizations` | `AuthorizationInput[]` | Yes | Authorizations to build | Each `AuthorizationInput`: | Field | Type | Required | Description | | --------------------- | --------------------- | -------- | ------------------------------------------------- | | `key` | `string` | Yes | Authorization key from `dex.authorizations[].key` | | `params` | `object` | No | Authorization-specific parameters | ### Response `201` ```json theme={null} { "actions": [ { "action": "ApproveAgent", "description": "Approve agent wallet for trading on your behalf", "typedData": { "domain": { "name": "HyperliquidSignTransaction", "version": "1", "chainId": 42161, "verifyingContract": "0x0000000000000000000000000000000000000000" }, "types": { "EIP712Domain": [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, { "name": "chainId", "type": "uint256" }, { "name": "verifyingContract", "type": "address" } ], "HyperliquidTransaction:ApproveAgent": [ { "name": "hyperliquidChain", "type": "string" }, { "name": "agentAddress", "type": "address" }, { "name": "agentName", "type": "string" }, { "name": "nonce", "type": "uint64" } ] }, "primaryType": "HyperliquidTransaction:ApproveAgent", "message": { "hyperliquidChain": "Mainnet", "agentAddress": "0x5678901234abcdef5678901234abcdef56789012", "agentName": "LI.FI Agent", "nonce": 1234567890 } } } ] } ``` If an authorization is already active, it will not appear in the `actions` array. An empty `actions` array means all requested authorizations are already satisfied. **SDK:** [`createAuthorization()`](/sdk/authorization#using-service-functions-advanced), [`perps.buildAuthorization()`](/sdk/authorization#buildauthorization) *** ## POST /authorization Submit signed authorizations. ``` POST /v1/perps/authorization ``` ### Request Body ```json theme={null} { "dex": "hyperliquid", "address": "0x1234567890abcdef1234567890abcdef12345678", "actions": [ { "action": "ApproveAgent", "typedData": { "...original typedData from createAuthorization..." }, "signature": "0xabcd1234..." }, { "action": "ApproveBuilderFee", "typedData": { "...original typedData from createAuthorization..." }, "signature": "0xefgh5678..." } ] } ``` | Field | Type | Required | Description | | ---------------------------- | ------------------------------------ | -------- | ---------------------------------------------------------------------------------------- | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address (account owner) | | `signerAddress` | `string` | No | Address that signed the payloads. Required for `USER_AGENT` mode. Defaults to `address`. | | `actions` | `SignedAuthorization[]` | Yes | Signed authorization payloads | Each `SignedAuthorization`: | Field | Type | Required | Description | | ------------------------ | --------------------- | -------- | ------------------------------------------------ | | `action` | `string` | Yes | Authorization action type | | `typedData` | `object` | Yes | Original `typedData` from `/createAuthorization` | | `signature` | `string` | Yes | User's signature of the `typedData` | ### Response `202` ```json theme={null} { "results": [ { "action": "ApproveAgent", "success": true }, { "action": "ApproveBuilderFee", "success": true } ] } ``` Each `AuthorizationResult`: | Field | Type | Description | | ---------------------- | ---------------------- | -------------------------------------- | | `action` | `string` | Authorization action type | | `success` | `boolean` | Whether the authorization was accepted | | `error` | `string` | Error message (if failed) | This endpoint returns `202 Accepted`. On-chain processing is asynchronous and typically completes within the next block. Poll `GET /account` to verify authorizations are active. ### Response `400` Validation error (invalid parameters or malformed request). **SDK:** [`submitAuthorization()`](/sdk/authorization#using-service-functions-advanced), [`perps.submitAuthorizations()`](/sdk/authorization#submitauthorizations) # Introduction Source: https://public-perps-docs.mintlify.app/api-reference/introduction LI.FI Perps API overview, authentication, and request format The LI.FI Perps API provides REST endpoints for perpetual futures trading across multiple DEXes. ## Base URLs | Environment | URL | | ----------- | ----------------------------------- | | Development | `https://develop.li.quest/v1/perps` | ## Authentication All requests require an API key. Register at the [LI.FI Partner Portal](https://portal.li.fi/) to get your API key and integrator name. Pass the following headers with every request: ``` x-lifi-api-key: your-api-key x-lifi-integrator: your-app-name ``` | Header | Required | Description | | ------------------- | -------- | ------------------------------------------------------------ | | `x-lifi-api-key` | Yes | API key provided by LI.FI (enforced at infrastructure level) | | `x-lifi-integrator` | No | Your integrator identifier for tracking and analytics | In the SDK, pass these as configuration options: ```typescript theme={null} import { createPerpsClient } from '@lifi/perps-sdk'; const client = createPerpsClient({ integrator: 'your-app-name', apiKey: 'your-api-key', }); ``` Never expose your `x-lifi-api-key` in client-side code (browser JavaScript, frontend bundles). The key is visible in browser developer tools and network tabs. For frontend integrations, proxy API calls through your backend to keep the key secret. ## Request Format * All `POST` request bodies use `application/json` * Query parameters are used for `GET` requests * String numeric values (prices, sizes, amounts) avoid floating-point precision issues ## Response Format Successful responses return JSON with appropriate HTTP status codes: | Status | Meaning | | ------ | ---------------------------------------------------------- | | `200` | Success — response body contains requested data | | `201` | Created — resource created and ready for next step | | `202` | Accepted — operation submitted for asynchronous processing | ## Response Headers All responses include the `x-lifi-requestid` header for request correlation and debugging: ``` x-lifi-requestid: req_abc123xyz789 ``` Include the `x-lifi-requestid` value when reporting issues to LI.FI support. This ID is logged server-side and allows us to trace your specific request. ## Error Format All errors follow the same structure with **numeric error codes** (2000+ range): ```json theme={null} { "code": 2002, "tool": "hyperliquid", "message": "Invalid request parameters" } ``` | Field | Type | Description | | ---------------------- | ---------------------- | ---------------------------------------------------- | | `code` | `integer` | Numeric error code (see [Error Codes](/error-codes)) | | `tool` | `string` | The DEX that generated the error | | `message` | `string` | Human-readable error description | ### HTTP Error Status Codes | Status | Description | | ------ | ----------------------------------------------------- | | `400` | Validation error — invalid parameters | | `401` | Authentication error — invalid signature | | `403` | Forbidden — agent not authorized | | `404` | Not found — market, order, or resource does not exist | | `408` | Timeout — request or upstream operation timed out | | `409` | Conflict — nonce already used | | `410` | Gone — nonce expired, retry the operation | | `422` | Unprocessable — insufficient margin or balance | | `424` | Failed dependency — upstream DEX error | ## Rate Limits Rate limits are applied per API key. Contact [LI.FI support](https://portal.li.fi/) for rate limit details and higher tier access. ## Endpoints Overview ### Market Data (Public) | Method | Path | Description | | ------ | --------------------- | ------------------------------------------- | | `GET` | `/dexes` | List DEX platforms | | `GET` | `/markets` | List all markets (optional `symbol` filter) | | `GET` | `/prices` | Get all mid prices | | `GET` | `/ohlcv/{symbol}` | Get OHLCV chart data | | `GET` | `/orderbook/{symbol}` | Get orderbook snapshot | ### Authorization | Method | Path | Description | | ------ | ---------------------- | ---------------------------- | | `POST` | `/createAuthorization` | Build authorization payloads | | `POST` | `/authorization` | Submit signed authorizations | ### Withdrawal | Method | Path | Description | | ------ | ------------------- | ------------------------ | | `POST` | `/createWithdrawal` | Build withdrawal payload | | `POST` | `/withdrawal` | Submit signed withdrawal | ### Account | Method | Path | Description | | ------ | ---------- | ------------------- | | `GET` | `/account` | Get account summary | | `GET` | `/history` | Get order history | ### Trading | Method | Path | Description | | ------ | -------------- | -------------------------- | | `POST` | `/createOrder` | Build order payloads | | `POST` | `/cancelOrder` | Build cancel payloads | | `POST` | `/order` | Submit signed order/cancel | | `GET` | `/order/{id}` | Get order details | # Market Data API Source: https://public-perps-docs.mintlify.app/api-reference/market-data Public endpoints for market information, prices, OHLCV, and orderbooks Public endpoints for market information. No authentication required beyond the API key header. *** ## GET /dexes List available perpetual DEX platforms. ``` GET /v1/perps/dexes ``` ### Parameters | Name | In | Type | Required | Description | | -------------------------------- | ------ | --------------------- | -------- | --------------------- | | `x-lifi-api-key` | header | `string` | Yes | API key | | `x-lifi-integrator` | header | `string` | No | Integrator identifier | ### Response `200` ```json theme={null} { "dexes": [ { "key": "hyperliquid", "name": "Hyperliquid", "logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/dexes/hyperliquid.svg", "authorizations": [ { "key": "ApproveAgent", "name": "Approve Agent", "params": [ { "name": "agentAddress", "type": "string", "required": true } ] }, { "key": "ApproveBuilderFee", "name": "Approve Builder Fee", "params": [] } ], "wsUrl": "wss://api.hyperliquid.xyz/ws", "extraData": {} } ] } ``` The `wsUrl` and `extraData` fields are optional — they may be absent for DEXes that don't support WebSocket streaming or have no extra configuration. **SDK:** [`getDexes()`](/sdk/market-data#getdexes) *** ## GET /markets List all perpetual markets for a DEX. ``` GET /v1/perps/markets?dex=hyperliquid ``` ### Parameters | Name | In | Type | Required | Description | | -------------------------------- | ------ | --------------------- | -------- | --------------------- | | `dex` | query | `string` | Yes | DEX identifier | | `x-lifi-api-key` | header | `string` | Yes | API key | | `x-lifi-integrator` | header | `string` | No | Integrator identifier | ### Response `200` ```json theme={null} { "markets": [ { "symbol": "BTC", "name": "Bitcoin", "logoURI": "https://static.debank.com/image/token/logo_url/btc/d3c8b30d5b8f7c0e82e87e1c8f4e5c5a.png", "dex": "hyperliquid", "szDecimals": 5, "maxLeverage": 50, "onlyIsolated": false, "funding": { "rate": "0.0001", "nextFundingTime": 1704110400000 }, "openInterest": "1250000000", "volume24h": "5000000000", "assetId": 0, "markPrice": "95000.50" } ] } ``` **SDK:** [`getMarkets()`](/sdk/market-data#getmarkets) *** ## GET /markets/ Get details for a single market. ``` GET /v1/perps/markets/BTC?dex=hyperliquid ``` ### Parameters | Name | In | Type | Required | Description | | -------------------------------- | ------ | --------------------- | -------- | --------------------------- | | `symbol` | path | `string` | Yes | Market symbol (e.g., `BTC`) | | `dex` | query | `string` | Yes | DEX identifier | | `x-lifi-api-key` | header | `string` | Yes | API key | | `x-lifi-integrator` | header | `string` | No | Integrator identifier | ### Response `200` Returns a single `Market` object (same shape as items in the `/markets` response). ```json theme={null} { "symbol": "BTC", "name": "Bitcoin", "logoURI": "https://static.debank.com/image/token/logo_url/btc/d3c8b30d5b8f7c0e82e87e1c8f4e5c5a.png", "dex": "hyperliquid", "szDecimals": 5, "maxLeverage": 50, "onlyIsolated": false, "funding": { "rate": "0.0001", "nextFundingTime": 1704110400000 }, "openInterest": "1250000000", "volume24h": "5000000000", "assetId": 0, "markPrice": "95000.50" } ``` ### Response `404` Market not found error. **SDK:** [`getMarket()`](/sdk/market-data#getmarket) *** ## GET /prices Get current mid prices for all markets. Lightweight endpoint for frequent polling. ``` GET /v1/perps/prices?dex=hyperliquid ``` ### Parameters | Name | In | Type | Required | Description | | -------------------------------- | ------ | --------------------- | -------- | ----------------------------------------------------------- | | `dex` | query | `string` | Yes | DEX identifier | | `symbols` | query | `string` | No | Comma-separated list of symbols to filter (e.g., `BTC,ETH`) | | `x-lifi-api-key` | header | `string` | Yes | API key | | `x-lifi-integrator` | header | `string` | No | Integrator identifier | ### Response `200` ```json theme={null} { "prices": { "BTC": "95000.50", "ETH": "3200.25", "SOL": "185.75" } } ``` **SDK:** [`getPrices()`](/sdk/market-data#getprices) *** ## GET /ohlcv/ Get OHLCV candle data for charts. ``` GET /v1/perps/ohlcv/BTC?dex=hyperliquid&interval=1h&limit=100 ``` ### Parameters | Name | In | Type | Required | Description | | -------------------------------- | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------ | | `symbol` | path | `string` | Yes | Market symbol | | `dex` | query | `string` | Yes | DEX identifier | | `interval` | query | `string` | Yes | Candle interval: `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M` | | `startTime` | query | `integer` | No | Start timestamp (ms) | | `endTime` | query | `integer` | No | End timestamp (ms) | | `limit` | query | `integer` | No | Max candles (default 100, max 1000) | | `x-lifi-api-key` | header | `string` | Yes | API key | | `x-lifi-integrator` | header | `string` | No | Integrator identifier | ### Response `200` ```json theme={null} { "dex": "hyperliquid", "symbol": "BTC", "interval": "1h", "candles": [ { "t": 1704067200000, "o": "94500.00", "h": "95200.00", "l": "94300.00", "c": "95000.50", "v": "125000000" } ] } ``` ### Response `404` Market not found error. **SDK:** [`getOhlcv()`](/sdk/market-data#getohlcv) *** ## GET /orderbook/ Get current orderbook snapshot. ``` GET /v1/perps/orderbook/BTC?dex=hyperliquid&depth=20 ``` ### Parameters | Name | In | Type | Required | Description | | -------------------------------- | ------ | ---------------------- | -------- | ---------------------------------- | | `symbol` | path | `string` | Yes | Market symbol | | `dex` | query | `string` | Yes | DEX identifier | | `depth` | query | `integer` | No | Price levels (default 20, max 100) | | `x-lifi-api-key` | header | `string` | Yes | API key | | `x-lifi-integrator` | header | `string` | No | Integrator identifier | ### Response `200` ```json theme={null} { "dex": "hyperliquid", "symbol": "BTC", "bids": [ { "price": "94999.00", "size": "1.5" }, { "price": "94998.00", "size": "2.3" } ], "asks": [ { "price": "95001.00", "size": "0.8" }, { "price": "95002.00", "size": "1.2" } ], "timestamp": 1704067200000 } ``` ### Response `404` Market not found error. **SDK:** [`getOrderbook()`](/sdk/market-data#getorderbook) # OpenAPI Specification Source: https://public-perps-docs.mintlify.app/api-reference/openapi-spec Download the LI.FI Perps API OpenAPI specification for integration with SDKs and tools ## LI.FI Perps OpenAPI Specification The LI.FI Perps API follows the OpenAPI 3.0 specification. You can use this spec to: * Generate client SDKs in any language * Import into API testing tools (Postman, Insomnia, etc.) * Integrate with AI agents and LLM tools * Validate requests and responses ## Download Download the Perps API OpenAPI specification ## API Base URLs | Environment | Base URL | | ----------- | ----------------------------------- | | Development | `https://develop.li.quest/v1/perps` | ## Specification Details * **OpenAPI Version**: 3.0.2 * **Endpoints**: Market data, authorization, account, and trading * **Format**: YAML # Trading API Source: https://public-perps-docs.mintlify.app/api-reference/trading Endpoints for order placement, cancellation, and status queries Endpoints for order placement, cancellation, and status queries. All mutating operations follow the **create → sign → submit** pattern. These endpoints are intended to be used via [`@lifi/perps-sdk`](https://www.npmjs.com/package/@lifi/perps-sdk). The typed data signing and submission flow is complex and best handled by the SDK. *** ## POST /createOrder Build order payloads for signing. Returns an array of `typedData` payloads that must be signed before submitting to `POST /order`. ``` POST /v1/perps/createOrder ``` ### Flow 1. Call this endpoint with order parameters 2. Sign each `typedData` in the response array 3. Submit all signatures to `POST /order` 4. Query `GET /order/{id}` to check status ### Request Body **Market order with TP/SL:** ```json theme={null} { "dex": "hyperliquid", "address": "0x1234567890abcdef1234567890abcdef12345678", "symbol": "BTC", "side": "BUY", "type": "MARKET", "size": "0.1", "price": "95500.00", "leverage": 10, "reduceOnly": false, "takeProfit": { "triggerPrice": "100000.00" }, "stopLoss": { "triggerPrice": "90000.00", "limitPrice": "89800.00" } } ``` **Simple limit order:** ```json theme={null} { "dex": "hyperliquid", "address": "0x1234567890abcdef1234567890abcdef12345678", "symbol": "ETH", "side": "BUY", "type": "LIMIT", "size": "1.0", "price": "3150.00", "reduceOnly": false, "timeInForce": "GTC" } ``` ### Request Fields | Field | Type | Required | Description | | ---------------------------- | -------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------ | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address (account owner) | | `signerAddress` | `string` | No | Address that will sign the payloads. Required for `USER_AGENT` mode (set to agent address). Defaults to `address`. | | `symbol` | `string` | Yes | Trading symbol | | `side` | `string` | Yes | `BUY` or `SELL` | | `type` | `string` | Yes | `MARKET` or `LIMIT` | | `size` | `string` | Yes | Order size | | `price` | `string` | Yes | Limit price (LIMIT) or slippage limit (MARKET) | | `leverage` | `integer` | No | Leverage to set (generates `updateLeverage` action if different) | | `reduceOnly` | `boolean` | No | Only reduce position (default `false`) | | `timeInForce` | `string` | No | `GTC` (default), `IOC`, `POST_ONLY`, `GTT` | | `expiresAt` | `string` | No | ISO 8601 expiry for GTT orders | | `takeProfit` | `TriggerOrderInput` | No | Take profit trigger | | `stopLoss` | `TriggerOrderInput` | No | Stop loss trigger | `TriggerOrderInput`: | Field | Type | Required | Description | | --------------------------- | --------------------- | -------- | ----------------------------------------------- | | `triggerPrice` | `string` | Yes | Price at which the order triggers | | `limitPrice` | `string` | No | Execution limit price (market order if omitted) | ### Response `201` ```json theme={null} { "actions": [ { "action": "updateLeverage", "description": "Set BTC leverage to 10x", "typedData": { "..." } }, { "action": "placeOrder", "description": "Place BUY 0.1 BTC @ 95500.00", "typedData": { "domain": { "name": "HyperliquidSignTransaction", "version": "1", "chainId": 42161, "verifyingContract": "0x0000000000000000000000000000000000000000" }, "types": { "EIP712Domain": [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, { "name": "chainId", "type": "uint256" }, { "name": "verifyingContract", "type": "address" } ] }, "primaryType": "Agent", "message": { "source": "a", "connectionId": "0x..." } } }, { "action": "placeTriggerOrder", "description": "Take profit at 100000.00", "typedData": { "..." } }, { "action": "placeTriggerOrder", "description": "Stop loss at 90000.00 (limit 89800.00)", "typedData": { "..." } } ] } ``` ### Action Types | Action | When Generated | | ------------------- | ---------------------------------------------------- | | `updateLeverage` | If `leverage` differs from current position leverage | | `placeOrder` | Always (the main order) | | `placeTriggerOrder` | Once per TP/SL specified | ### Response `400` Validation error. **SDK:** [`createOrder()`](/sdk/trading#user-mode-manual-signing), [`perps.placeOrder()`](/sdk/trading#user_agent-mode-automatic-signing) *** ## POST /cancelOrder Build cancel payloads for signing. ``` POST /v1/perps/cancelOrder ``` ### Flow 1. Call this endpoint with order ID(s) to cancel 2. Sign each `typedData` in the response array 3. Submit all signatures to `POST /order` ### Request Body ```json theme={null} { "dex": "hyperliquid", "address": "0x1234567890abcdef1234567890abcdef12345678", "ids": ["0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d", "12345678"] } ``` | Field | Type | Required | Description | | ---------------------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------- | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address (account owner) | | `signerAddress` | `string` | No | Address that will sign the payloads. Required for `USER_AGENT` mode. Defaults to `address`. | | `ids` | `string[]` | Yes | Order IDs to cancel (`orderId` from the submit response) | ### Response `201` ```json theme={null} { "actions": [ { "action": "cancelOrder", "description": "Cancel order 0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d", "typedData": { "..." } }, { "action": "cancelOrder", "description": "Cancel order 12345678", "typedData": { "..." } } ] } ``` ### Response `400` Validation error. ### Response `404` Order not found. **SDK:** [`cancelOrder()`](/sdk/trading#user-mode-manual-signing-1), [`perps.cancelOrders()`](/sdk/trading#user_agent-mode) *** ## POST /order Submit signed order or cancel payloads from `/createOrder` or `/cancelOrder`. ``` POST /v1/perps/order ``` ### Request Body ```json theme={null} { "dex": "hyperliquid", "address": "0x1234567890abcdef1234567890abcdef12345678", "actions": [ { "action": "updateLeverage", "typedData": { "...typedData from createOrder action 1..." }, "signature": "0xabcd1234..." }, { "action": "placeOrder", "typedData": { "...typedData from createOrder action 2..." }, "signature": "0xefgh5678..." } ] } ``` | Field | Type | Required | Description | | ---------------------------- | ---------------------------------- | -------- | ---------------------------------------------------------------------------------------- | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address (account owner) | | `signerAddress` | `string` | No | Address that signed the payloads. Required for `USER_AGENT` mode. Defaults to `address`. | | `actions` | `SignedOrderAction[]` | Yes | Signed actions from `/createOrder` or `/cancelOrder` | Each `SignedOrderAction`: | Field | Type | Required | Description | | ------------------------ | --------------------- | -------- | ---------------------------------------------------------- | | `action` | `string` | Yes | Action type from the create response | | `typedData` | `object` | Yes | Original `typedData` from `/createOrder` or `/cancelOrder` | | `signature` | `string` | Yes | User's or agent's signature of the `typedData` | ### Response `202` ```json theme={null} { "results": [ { "action": "updateLeverage", "success": true }, { "action": "placeOrder", "success": true, "orderId": "12345678" }, { "action": "placeTriggerOrder", "success": true, "orderId": "87654321" } ] } ``` Each `OrderActionResult`: | Field | Type | Description | | ---------------------- | ---------------------- | --------------------------------------------------------------------- | | `action` | `string` | `updateLeverage`, `placeOrder`, `placeTriggerOrder`, or `cancelOrder` | | `success` | `boolean` | Whether the action was accepted | | `orderId` | `string` | Order ID (for `placeOrder` and `placeTriggerOrder`) | | `error` | `string` | Error message if failed | ### Response `400` Validation error. ### Response `401` Authentication error (invalid signature). ### Response `422` Insufficient margin for the order. **SDK:** [`submitOrder()`](/sdk/trading#user-mode-manual-signing), [`perps.submitSignedOrder()`](/sdk/trading#submitsignedorder) *** ## GET /order/ Get the status and details of a specific order. ``` GET /v1/perps/order/12345678?dex=hyperliquid&address=0x1234... ``` ### Parameters | Name | In | Type | Required | Description | | -------------------------------- | ------ | --------------------- | -------- | --------------------------------------------- | | `id` | path | `string` | Yes | Order ID (`orderId` from the submit response) | | `dex` | query | `string` | Yes | DEX identifier | | `address` | query | `string` | Yes | User's wallet address | | `x-lifi-api-key` | header | `string` | Yes | API key | | `x-lifi-integrator` | header | `string` | No | Integrator identifier | ### Response `200` ```json theme={null} { "orderId": "12345678", "symbol": "BTC", "side": "BUY", "type": "MARKET", "price": "95500.00", "originalSize": "0.1", "remainingSize": "0.0", "filledSize": "0.1", "timeInForce": "GTC", "reduceOnly": false, "isTrigger": false, "status": "FILLED", "averagePrice": "95050.00", "createdAt": "2025-01-15T10:30:00Z", "updatedAt": "2025-01-15T10:30:01Z" } ``` ### Order Schema | Field | Type | Description | | ------------------------------- | ---------------------- | --------------------------------------- | | `orderId` | `string` | Order ID | | `symbol` | `string` | Trading symbol | | `side` | `string` | `BUY` or `SELL` | | `type` | `string` | `MARKET` or `LIMIT` | | `price` | `string` | Limit price or execution price | | `originalSize` | `string` | Original order size | | `remainingSize` | `string` | Unfilled quantity | | `filledSize` | `string` | Filled quantity | | `timeInForce` | `string` | `GTC`, `IOC`, `POST_ONLY`, `GTT` | | `expiresAt` | `string` | Expiration time (GTT orders) | | `reduceOnly` | `boolean` | Whether reduce-only | | `isTrigger` | `boolean` | Whether this is a trigger order (TP/SL) | | `triggerPrice` | `string` | Trigger activation price | | `triggerCondition` | `string` | `ABOVE` or `BELOW` | | `status` | `string` | Order status (see below) | | `averagePrice` | `string` | Average fill price | | `createdAt` | `string` | ISO 8601 creation timestamp | | `updatedAt` | `string` | ISO 8601 last update timestamp | ### 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) | ### Response `404` ```json theme={null} { "code": 2024, "tool": "hyperliquid", "message": "Order not found" } ``` **SDK:** [`getOrder()`](/sdk/trading#getting-order-status) # WebSocket Source: https://public-perps-docs.mintlify.app/api-reference/websocket WebSocket streaming via direct DEX connections Data is streamed via WebSocket connections **directly to the DEX**, not through the LI.FI API. This provides the lowest possible latency for market data and user events. If you're using the `@lifi/perps-sdk`, the [`PerpsWsClient`](/sdk/streaming) handles all connection management, subscriptions, and data normalization automatically. ## Connection Discovery The WebSocket URL for each DEX is advertised via the `GET /dexes` endpoint in the optional `wsUrl` field. Not all DEXes may support WebSocket — check for its presence before attempting to connect. ``` GET /v1/perps/dexes ``` Connect directly to the returned `wsUrl`. The URL points to the DEX's native WebSocket endpoint — message formats, available channels, and rate limits are DEX-specific. ## Reconnection If the connection drops: 1. Wait with exponential backoff (start at 150ms, double each attempt, max 10s) 2. Reconnect to the same `wsUrl` 3. Re-send all active subscriptions The SDK's `PerpsWsClient` handles this automatically. ## Provider-Specific Protocol Each DEX has its own native WebSocket protocol, message formats, channels, and rate limits. See the provider pages for details: * [Hyperliquid — WebSocket Protocol](/providers/hyperliquid#websocket-protocol) # Withdrawal API Source: https://public-perps-docs.mintlify.app/api-reference/withdrawal Endpoints for withdrawing funds from a perps DEX to L1 Endpoints for building and submitting withdrawals from a perps DEX. The example requests and responses on this page use **Hyperliquid** (`dex: 'hyperliquid'`). Request/response shapes (typed data structure, destination chains, and processing behavior) vary by DEX. Replace the `dex` value with any supported DEX from `GET /dexes`. These endpoints are intended to be used via [`@lifi/perps-sdk`](https://www.npmjs.com/package/@lifi/perps-sdk). The typed data signing and submission flow is complex and best handled by the SDK. Withdrawals are **user-signed only**. Agents cannot initiate withdrawals — this is a security feature. The withdrawal typed data requires the user's wallet signature regardless of signing mode. *** ## POST /createWithdrawal Build a withdrawal payload for the user to sign. ``` POST /v1/perps/createWithdrawal ``` ### Flow 1. Call this endpoint with the withdrawal details (destination address and amount) 2. Backend builds the EIP-712 typed data for `HyperliquidTransaction:Withdraw` 3. Sign the `typedData` in the returned action with the user's wallet 4. Submit the signature to `POST /withdrawal` ### Request Body ```json theme={null} { "dex": "hyperliquid", "address": "0x1234567890abcdef1234567890abcdef12345678", "withdrawal": { "destination": "0x1234567890abcdef1234567890abcdef12345678", "amount": "100.0" } } ``` | Field | Type | Required | Description | | ------------------------------------- | --------------------- | -------- | ------------------------------------- | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address (account owner) | | `withdrawal` | `object` | Yes | Withdrawal details | | `withdrawal.destination` | `string` | Yes | Address to receive withdrawn funds | | `withdrawal.amount` | `string` | Yes | Amount to withdraw in USDC | ### Response `201` ```json theme={null} { "action": { "action": "Withdraw", "description": "Withdraw 100.0 USDC to 0x1234567890abcdef1234567890abcdef12345678", "typedData": { "domain": { "name": "HyperliquidSignTransaction", "version": "1", "chainId": 42161, "verifyingContract": "0x0000000000000000000000000000000000000000" }, "types": { "EIP712Domain": [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, { "name": "chainId", "type": "uint256" }, { "name": "verifyingContract", "type": "address" } ], "HyperliquidTransaction:Withdraw": [ { "name": "hyperliquidChain", "type": "string" }, { "name": "destination", "type": "string" }, { "name": "amount", "type": "string" }, { "name": "time", "type": "uint64" } ] }, "primaryType": "HyperliquidTransaction:Withdraw", "message": { "hyperliquidChain": "Mainnet", "destination": "0x1234567890abcdef1234567890abcdef12345678", "amount": "100.0", "time": 1234567890 } } } } ``` **SDK:** [`createWithdrawal()`](/sdk/withdrawal#using-service-functions-advanced), [`perps.buildWithdrawal()`](/sdk/withdrawal#buildwithdrawal) *** ## POST /withdrawal Submit a signed withdrawal. ``` POST /v1/perps/withdrawal ``` ### Request Body ```json theme={null} { "dex": "hyperliquid", "address": "0x1234567890abcdef1234567890abcdef12345678", "action": { "action": "Withdraw", "typedData": { "...original typedData from createWithdrawal..." }, "signature": "0xabcd1234..." } } ``` | Field | Type | Required | Description | | ------------------------------- | --------------------- | -------- | --------------------------------------------- | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address | | `action` | `object` | Yes | Signed withdrawal payload | | `action.action` | `string` | Yes | Action type from `/createWithdrawal` | | `action.typedData` | `object` | Yes | Original `typedData` from `/createWithdrawal` | | `action.signature` | `string` | Yes | User's signature of the `typedData` | ### Response `202` ```json theme={null} { "result": { "action": "Withdraw", "success": true } } ``` | Field | Type | Description | | ----------------------------- | ---------------------- | ----------------------------------- | | `result.action` | `string` | Action type | | `result.success` | `boolean` | Whether the withdrawal was accepted | | `result.error` | `string` | Error message (if failed) | This endpoint returns `202 Accepted`. Withdrawal processing is asynchronous. For Hyperliquid, the bridge transfer to Arbitrum typically takes 3–4 minutes. Poll `GET /account` to verify the balance change. ### Response `400` Validation error (invalid parameters, invalid amount, or malformed request). **SDK:** [`submitWithdrawal()`](/sdk/withdrawal#using-service-functions-advanced), [`perps.submitWithdrawal()`](/sdk/withdrawal#submitwithdrawal) # Action Pattern Source: https://public-perps-docs.mintlify.app/concepts/action-pattern The create → sign → submit pattern used by all mutating operations All mutating operations (orders, cancellations, authorizations, withdrawals) follow the same **create → sign → submit** pattern: ## 1. Create Call a create endpoint (`createOrder`, `cancelOrder`, `createAuthorization`, `createWithdrawal`) with your parameters. The backend returns an `actions[]` array, where each action contains: * `action` — The action type (e.g., `placeOrder`, `updateLeverage`, `placeTriggerOrder`) * `description` — Human-readable description * `typedData` — EIP-712 typed data to sign ## 2. Sign Sign each `typedData` payload. Who signs depends on the signing mode: * **USER mode**: The user signs with their connected wallet * **USER\_AGENT mode**: The SDK's local agent signs automatically ## 3. Submit Send the `actions[]` array to the submit endpoint (`POST /order` or `POST /authorization`). Each element is an object containing the `action` type, the original `typedData`, and the `signature`. ```typescript theme={null} // Each signed action has this shape: { action: 'placeOrder', typedData: { ... }, signature: '0x...' } ``` A single create call can return multiple actions. For example, placing an order with TP/SL and a leverage change produces `updateLeverage`, `placeOrder`, and `placeTriggerOrder` actions — all must be signed and submitted together. # Advanced Source: https://public-perps-docs.mintlify.app/concepts/advanced Request cancellation and advanced usage patterns ## Request Cancellation All service functions accept an optional `options` parameter with an `AbortSignal` for cancelling in-flight requests: ```typescript theme={null} const controller = new AbortController(); // Cancel after 5 seconds setTimeout(() => controller.abort(), 5000); const { markets } = await getMarkets(client, { dex: 'hyperliquid', }, { signal: controller.signal }); ``` When the signal fires, the underlying `fetch` is aborted and the promise rejects with an `AbortError`. # Authorization Flow Source: https://public-perps-docs.mintlify.app/concepts/authorization How DEX authorizations are discovered, built, signed, and submitted Before trading, the user must authorize certain actions on each DEX. The available authorizations are **discovered dynamically** from the `GET /dexes` endpoint. See [SDK / Authorization](/sdk/authorization) for the full implementation guide. ## Discovery Each DEX returns an `authorizations[]` array describing what approvals are needed: ```typescript theme={null} const { dexes } = await getDexes(client); const hl = dexes.find((d) => d.key === 'hyperliquid'); console.log(hl.authorizations); // [ // { key: 'ApproveAgent', name: 'Approve Agent', params: [{ name: 'agentAddress', type: 'string', required: true }] }, // { key: 'ApproveBuilderFee', name: 'Approve Builder Fee', params: [] }, // ] ``` ## Batch Authorization Authorizations are processed in batches: 1. **Create** — `POST /createAuthorization` with an array of desired authorizations 2. The backend checks which are already satisfied and returns only the ones that need signing 3. **Sign** — User signs each returned `typedData` with their wallet 4. **Submit** — `POST /authorization` with all signed payloads # Multi-DEX Source: https://public-perps-docs.mintlify.app/concepts/multi-dex Multi-DEX support for trading on multiple DEXes simultaneously ## Multi-DEX Support The SDK supports trading on multiple DEXes simultaneously. Each DEX has independent: * Signing mode (USER or USER\_AGENT) * Agent wallet (separate keypair per DEX) * Authorization state ```typescript theme={null} await perps.setSigningMode(userAddress, 'hyperliquid', 'USER_AGENT'); await perps.setSigningMode(userAddress, 'another-dex', 'USER'); // Trade on each independently await perps.placeOrder({ dex: 'hyperliquid', ... }); ``` # Signing Modes Source: https://public-perps-docs.mintlify.app/concepts/signing-modes USER and USER_AGENT signing modes, agent key storage, and key management The SDK supports two signing modes that determine who signs trading transactions. Signing mode is an **SDK-level concept only** — the API accepts `actions[]` (each containing `action`, `typedData`, and `signature`) and does not need to know who signed. | Mode | Description | Trade UX | Best for | | ------------ | ---------------------------------------------------- | ---------------------- | ------------------------------------ | | `USER` | User signs every action with their wallet | Wallet popup per order | Maximum security, infrequent trading | | `USER_AGENT` | SDK-managed agent wallet signs on behalf of the user | No popups after setup | Active trading, seamless UX | ## USER mode The user signs each piece of `typedData` directly with their connected wallet (e.g., MetaMask, WalletConnect). Every order placement and cancellation requires a wallet interaction. ```typescript theme={null} await perps.setSigningMode(userAddress, 'hyperliquid', 'USER'); ``` ## USER\_AGENT mode The SDK generates a local agent keypair, stored in the browser's localStorage. The user approves this agent once via a wallet signature, then the agent can sign all subsequent trading actions without wallet popups. ```typescript theme={null} await perps.setSigningMode(userAddress, 'hyperliquid', 'USER_AGENT'); // Agent keypair is generated and stored automatically ``` The agent wallet's private key never leaves the client. It's stored via a pluggable storage adapter and is scoped to a specific user + DEX pair. ## Agent Key Storage By default, agent keys are persisted using the browser's `localStorage`. For server-side environments (Node.js, Deno) or custom persistence, pass a `storage` adapter: ```typescript theme={null} import { createPerpsClient, PerpsClient, createMemoryStorage, localStorageAdapter, } from '@lifi/perps-sdk'; // Browser (default) — uses localStorage const client = createPerpsClient({ integrator: 'my-app' }); // Testing / ephemeral — keys are lost when the process exits const client = createPerpsClient({ integrator: 'my-app', storage: createMemoryStorage(), }); // Same options work with PerpsClient const perps = new PerpsClient({ integrator: 'my-app', storage: createMemoryStorage(), }); ``` For production server-side use, implement the `StorageAdapter` interface backed by your own datastore (e.g., Redis, database): ```typescript theme={null} import type { StorageAdapter } from '@lifi/perps-sdk'; const redisStorage: StorageAdapter = { get: async (key) => await redis.get(key), set: async (key, value) => { await redis.set(key, value) }, remove: async (key) => { await redis.del(key) }, }; const perps = new PerpsClient({ integrator: 'my-app', storage: redisStorage, }); ``` ## Agent Key Management The `AgentManager` is accessible via `client.agentManager` (from `createPerpsClient`) or `perps.client.agentManager` (from `PerpsClient`). It provides methods for importing, inspecting, and removing agent keys: ```typescript theme={null} const agentManager = perps.client.agentManager; // Import an existing agent keypair (e.g., restoring from backup) await agentManager.importAgent(userAddress, 'hyperliquid', '0xprivatekey...'); // Check if an agent exists for a user + DEX pair const exists = await agentManager.hasAgent(userAddress, 'hyperliquid'); // Get or create an agent (creates a new keypair if none exists) const agent = await agentManager.getOrCreateAgent(userAddress, 'hyperliquid'); console.log(agent.address); // Agent's public address // Remove an agent (also resets signing mode to USER) await agentManager.removeAgent(userAddress, 'hyperliquid'); ``` # Streaming Source: https://public-perps-docs.mintlify.app/concepts/streaming Live data via WebSocket connections directly to the DEX The SDK supports WebSocket connections for streaming live data. Unlike REST operations that go through the LI.FI Perps API, WebSocket connections are made **directly to the DEX** for lowest latency. The SDK discovers the WebSocket URL from the `GET /dexes` response and handles connection management, reconnection, and data normalization automatically. All incoming data is normalized to the same types used by REST responses (`OrderbookResponse`, `Order`, `Position`, etc.), so your application logic works identically whether the data came from a REST call or a WebSocket event. See [SDK / Streaming](/sdk/streaming) for usage details and available channels. # Error Codes Source: https://public-perps-docs.mintlify.app/error-codes Error codes, HTTP status codes, and troubleshooting guide All API errors return a `PerpsError` with a numeric `code`, the originating `tool` (DEX), and a human-readable `message`. ```json theme={null} { "code": 2021, "tool": "hyperliquid", "message": "Insufficient margin for order" } ``` In the SDK, errors are wrapped in `PerpsSDKError` which carries the numeric error code: ```typescript theme={null} import { PerpsSDKError, PerpsErrorCode } from '@lifi/perps-sdk'; try { await perps.placeOrder({ ... }); } catch (error) { if (error instanceof PerpsSDKError) { console.log(error.code); // 2021 console.log(error.message); // 'Insufficient margin for order' // Use named constants for readable error handling if (error.code === PerpsErrorCode.InsufficientMargin) { // Handle insufficient margin } } } ``` ### Error Types For more specific handling, the SDK exports specialized error classes and utilities to inspect the error chain: ```typescript theme={null} import { PerpsSDKError, HTTPError, AgentError, findErrorType, } from '@lifi/perps-sdk'; try { await perps.placeOrder({ ... }); } catch (error) { // HTTPError — API returned a non-2xx response const httpErr = findErrorType(error, HTTPError); if (httpErr) { console.log(httpErr.status); // HTTP status code (e.g., 422) console.log(httpErr.responseBody); // Full API error: { code, tool, message } } // AgentError — agent not found or not authorized const agentErr = findErrorType(error, AgentError); if (agentErr) { // Re-run the authorization flow } } ``` | Class | When thrown | Useful fields | | ------------------------------ | ----------------------------------------- | ------------------------------- | | `PerpsSDKError` | Top-level wrapper for all SDK errors | `code`, `message`, `cause` | | `HTTPError` | API returned a non-2xx response | `status`, `url`, `responseBody` | | `AgentError` | Agent not found or not authorized for DEX | `code`, `message` | | `ServerError` | Network failure or timeout | `code`, `message` | | `ValidationError` | Client-side parameter validation failed | `message` | ## Error Code Ranges Error codes use the **2000+ range** to avoid collision with the main LI.FI API (which uses 1000+): | Range | Category | Description | | --------- | -------------- | ------------------------------------ | | 2000-2009 | Base errors | Server errors, validation, timeout | | 2010-2019 | Auth errors | Signature and authorization failures | | 2020-2039 | Trading errors | Order execution and market errors | | 2040-2049 | Nonce errors | Nonce validation and expiry | | 2050-2059 | Payload errors | Payload integrity failures | | 2060-2069 | Routing errors | Invalid API routes | ## Error Code Reference ### Base Errors (2000-2009) | Code | Name | HTTP | Description | | ----------------- | ------------------------------ | ---- | ------------------------- | | 2000 | `DefaultError` | 500 | Unexpected server error | | 2001 | `ServerError` | 500 | Internal server error | | 2002 | `ValidationError` | 400 | Request validation failed | | 2003 | `TimeoutError` | 408 | Request timeout | | 2004 | `ThirdPartyError` | 424 | External service failure | ### Authentication Errors (2010-2019) | Code | Name | HTTP | Description | | ----------------- | -------------------------------- | ---- | ----------------------------- | | 2010 | `SignatureInvalid` | 401 | Signature verification failed | | 2011 | `AgentUnauthorized` | 403 | Agent wallet not approved | ### Trading Errors (2020-2039) | Code | Name | HTTP | Description | | ----------------- | ---------------------------------- | ---- | ------------------------------- | | 2020 | `ExchangeRejected` | 422 | DEX rejected the operation | | 2021 | `InsufficientMargin` | 422 | Not enough margin for the order | | 2022 | `InsufficientBalance` | 422 | Not enough balance | | 2023 | `MarketNotFound` | 404 | Unknown market/symbol | | 2024 | `OrderNotFound` | 404 | Order doesn't exist | | 2025 | `PositionNotFound` | 404 | Position doesn't exist | ### Nonce Errors (2040-2049) | Code | Name | HTTP | Description | | ----------------- | ------------------------------- | ---- | ----------------------- | | 2040 | `InvalidNonce` | 400 | Nonce validation failed | | 2041 | `NonceAlreadyUsed` | 409 | Nonce already consumed | | 2042 | `NonceExpired` | 410 | Nonce TTL exceeded | ### Payload Errors (2050-2059) | Code | Name | HTTP | Description | | ----------------- | ------------------------------ | ---- | ----------------------------------- | | 2050 | `PayloadMismatch` | 400 | Signed payload doesn't match stored | ### Routing Errors (2060-2069) | Code | Name | HTTP | Description | | ----------------- | ---------------------------- | ---- | ------------------------------------------- | | 2060 | `RouteNotFound` | 404 | Invalid API route / endpoint does not exist | ## SDK Error Code Constants The SDK exports `PerpsErrorCode` for type-safe error handling: ```typescript theme={null} import { PerpsErrorCode } from '@lifi/perps-sdk'; // Base errors PerpsErrorCode.DefaultError // 2000 PerpsErrorCode.ServerError // 2001 PerpsErrorCode.ValidationError // 2002 PerpsErrorCode.TimeoutError // 2003 PerpsErrorCode.ThirdPartyError // 2004 // Auth errors PerpsErrorCode.SignatureInvalid // 2010 PerpsErrorCode.AgentUnauthorized // 2011 // Trading errors PerpsErrorCode.ExchangeRejected // 2020 PerpsErrorCode.InsufficientMargin // 2021 PerpsErrorCode.InsufficientBalance // 2022 PerpsErrorCode.MarketNotFound // 2023 PerpsErrorCode.OrderNotFound // 2024 PerpsErrorCode.PositionNotFound // 2025 // Nonce errors PerpsErrorCode.InvalidNonce // 2040 PerpsErrorCode.NonceAlreadyUsed // 2041 PerpsErrorCode.NonceExpired // 2042 // Payload errors PerpsErrorCode.PayloadMismatch // 2050 // Routing errors PerpsErrorCode.RouteNotFound // 2060 ``` ## Troubleshooting ### ExchangeRejected (2020) The DEX backend rejected the request. Check: * Order parameters are valid (size within limits, price within bounds) * The market is active and not in maintenance * Request body matches the expected schema ### InsufficientMargin (2021) Not enough margin for the requested order. Check: * Account has sufficient USDC balance * Existing positions don't consume too much margin * Reduce leverage or order size ### InsufficientBalance (2022) Account lacks funds for the operation. Check: * Deposit sufficient funds to your DEX account * Check balances with `getAccount()` ### SignatureInvalid (2010) The signature could not be verified. Check: * The correct wallet/agent signed the `typedData` * The `typedData` was not modified after creation * The nonce has not expired (re-call `createOrder` or `createAuthorization` to get fresh data) ### AgentUnauthorized (2011) The agent wallet is not approved on the DEX. Check: * The authorization flow completed successfully (both `createAuthorization` and `submitAuthorization`) * The agent address matches what was authorized * The authorization has not been revoked ### MarketNotFound (2023) The symbol doesn't exist on the specified DEX. Check: * Use `getMarkets()` to list valid symbols * Symbol is case-sensitive (use `BTC`, not `btc`) * The market hasn't been delisted ### OrderNotFound (2024) The order ID doesn't exist. Check: * Use the correct `orderId` (from the submit response) * The order may have already been fully filled or cancelled * Verify the `dex` parameter matches where the order was placed ### PositionNotFound (2025) No position exists for the specified symbol. Check: * Verify you have an open position for this symbol * Use `getAccount()` to list current positions ### InvalidNonce (2040) The nonce in the request failed validation. Check: * The `typedData` was not modified after creation * Re-call the create endpoint to get fresh payloads ### NonceAlreadyUsed (2041) The nonce has already been consumed by a previous request. This typically happens when: * Submitting the same signed payload twice * Re-submitting after a successful operation Solution: Call the create endpoint again to get new payloads with fresh nonces. ### NonceExpired (2042) The nonce has exceeded its time-to-live. Payloads expire after a short period for security. Check: * Sign and submit payloads promptly after creation * Re-call the create endpoint to get fresh payloads ### PayloadMismatch (2050) The signed payload doesn't match what was stored server-side. This can happen if: * The `typedData` was modified before signing * A different payload was substituted Solution: Use the exact `typedData` returned from the create endpoint without modification. ### ThirdPartyError (2004) An external service (typically the DEX) returned an error. Check: * DEX may be experiencing issues * Retry after a short delay * Check DEX status pages for outages ### TimeoutError (2003) The request exceeded the timeout limit. Check: * Network connectivity * DEX may be experiencing high latency * Retry the operation # Getting Started Source: https://public-perps-docs.mintlify.app/getting-started Install and configure @lifi/perps-sdk ## Installation ```bash theme={null} # yarn yarn add @lifi/perps-sdk # pnpm pnpm add @lifi/perps-sdk # npm npm install @lifi/perps-sdk # bun bun add @lifi/perps-sdk ``` ## Configuration Initialize the Perps SDK client: ```typescript theme={null} import { createPerpsClient, DEFAULT_API_URL } from '@lifi/perps-sdk'; // Initialize the perps client const client = createPerpsClient({ integrator: 'your-app-name', apiKey: 'your-api-key', }); ``` The SDK targets `DEFAULT_API_URL` (`https://develop.li.quest/v1/perps`) by default. Override with `apiUrl` if needed: ```typescript theme={null} const client = createPerpsClient({ integrator: 'your-app-name', apiKey: 'your-api-key', apiUrl: 'https://develop.li.quest/v1/perps', }); ``` Get your integrator name and API key from the [LI.FI Partner Portal](https://portal.li.fi/). Both are required for API access. ## Quick Start Fetch available DEXes and markets: ```typescript theme={null} import { createPerpsClient, getDexes, getMarkets, getPrices } from '@lifi/perps-sdk'; const client = createPerpsClient({ integrator: 'your-app-name', apiKey: 'your-api-key', }); // List available DEXes const { dexes } = await getDexes(client); console.log(dexes.map((d) => d.name)); // ['Hyperliquid', ...] // List markets on a DEX const { markets } = await getMarkets(client, { dex: 'hyperliquid' }); console.log(markets.map((m) => m.symbol)); // ['BTC', 'ETH', 'SOL', ...] // Get current prices const { prices } = await getPrices(client, { dex: 'hyperliquid' }); console.log(prices); // { BTC: '95000.50', ETH: '3200.25', ... } ``` For a complete end-to-end example including wallet setup, authorization, and placing an order, see [SDK / Trading](/sdk/trading#end-to-end-example). ## Next steps * [Concepts / Signing Modes](/concepts/signing-modes) — USER vs USER\_AGENT, agent key storage and management * [Concepts / Action Pattern](/concepts/action-pattern) — The create → sign → submit pattern * [SDK / Authorization](/sdk/authorization) — Complete authorization flow with sequence diagram * [SDK / Trading](/sdk/trading) — All order types, features, and a full end-to-end example * [SDK / Market Data](/sdk/market-data) — Prices, orderbooks, and charts # LLMs Source: https://public-perps-docs.mintlify.app/llms Machine-readable documentation for LLMs and AI agents ## LLM-Friendly Documentation These files follow the [llms.txt](https://llmstxt.org/) convention — plain-text documentation optimized for consumption by large language models and AI coding agents. Use these when building AI-powered integrations, feeding context to coding assistants, or working with agent frameworks that accept documentation as input. ## Files ### llms.txt Concise index of the LI.FI Perps SDK documentation. Contains a project summary, quick reference links, and a section-by-section table of contents pointing to detailed documentation. Best for: quick context loading, tool discovery, initial orientation. **URL:** [`https://public-perps-docs.mintlify.app/llms.txt`](/llms.txt) Download the llms.txt index file *** ### llms-full.txt Complete documentation in a single Markdown file (\~37 KB). Includes installation, concepts, full SDK reference, REST API reference, Hyperliquid provider details, error codes, and an end-to-end example — everything an LLM needs to write code against the Perps SDK. Best for: full context loading, code generation, comprehensive Q\&A. **URL:** [`https://public-perps-docs.mintlify.app/llms-full.txt`](/llms-full.txt) Download the complete LLM documentation # Overview Source: https://public-perps-docs.mintlify.app/overview Introduction to @lifi/perps-sdk `@lifi/perps-sdk` is a TypeScript SDK for perpetual futures trading across multiple decentralized exchanges through a single, unified interface. It handles trading, account management, and withdrawals. For **deposits** (swapping any token into USDC and bridging it to the venue), use the [`@lifi/sdk`](https://docs.li.fi/integrate-li.fi-js-sdk/install-li.fi-sdk) as a sibling dependency — both packages are required at the application level. ## Features * **Multi-DEX support** — Trade on Hyperliquid and other perpetual DEXes through one API * **Streaming** — Subscribe to live price feeds, orderbook updates, fills, and order status via WebSocket * **Two signing modes** — Choose between direct wallet signing (`USER`) or agent wallet signing (`USER_AGENT`) for a seamless experience * **Take profit & stop loss** — Attach TP/SL triggers to any order * **Agent wallets** — One-time approval, then trade without wallet popups * **Authorization discovery** — Dynamically discover required DEX authorizations via `GET /dexes` * **Unified order type** — Consistent order representation across all supported DEXes * **Withdrawals** — Built-in withdrawal flow via `/createWithdrawal` and `/withdrawal` endpoints * **LI.FI integration** — Use `@lifi/sdk` as a sibling dependency for deposits (ANY token → USDC) and bridging ## Architecture ```mermaid theme={null} graph TB APP["Your Application"] LIFI["@lifi/sdk
ANY token → USDC deposits · bridging"]:::existing LIFIAPI["LI.FI API
li.quest/v1/*"]:::existing SDK["@lifi/perps-sdk
Trading · Streaming · AgentWallet"] API["LI.FI Perps API
develop.li.quest/v1/perps/*"] APP --> LIFI LIFI --> LIFIAPI APP --> SDK SDK -->|REST| API SDK -.->|WebSocket Read| HL SDK -.->|WebSocket Read| FD API -->|Read/Write| HL["Hyperliquid"] API -->|Read/Write| FD["*Future DEX"] classDef existing stroke-dasharray: 5 5 ``` * **`@lifi/sdk`** handles deposits: swapping any token on any chain into USDC and bridging it to the DEX venue. Use this to fund a perps account. * **`@lifi/perps-sdk`** handles trading and withdrawals. It communicates with the **LI.FI Perps API**, which acts as a unified backend abstracting away the differences between underlying DEX protocols. Each DEX has its own signing requirements and authorization flows — the SDK handles these details transparently. **Hyperliquid:** Withdrawals are processed via the Perps SDK's `/createWithdrawal` and `/withdrawal` endpoints. This withdraws USDC from the Hyperliquid unified USDC balance to Arbitrum USDC. Withdrawal mechanics may differ for other DEXes in the future. ## Order Flow All mutating operations (orders, cancellations, authorizations) follow the **create → sign → submit** pattern: ```mermaid theme={null} sequenceDiagram participant App as Your App participant SDK as PerpsClient participant API as LI.FI Perps API participant DEX as DEX App->>SDK: placeOrder(params) SDK->>API: POST /createOrder API-->>SDK: actions[] with typedData SDK->>SDK: Sign each typedData SDK->>API: POST /order (signed actions) API->>DEX: Submit to chain DEX-->>API: Result API-->>SDK: results[] SDK-->>App: SubmitOrderResponse ``` In `USER_AGENT` mode, the SDK signs automatically with a local agent wallet. In `USER` mode, your application signs each `typedData` with the user's connected wallet. ## Next steps * [Getting Started](/getting-started) — Install and configure the SDK * [Concepts](/concepts/signing-modes) — Understand signing modes, authorization, and the action pattern * [SDK / Trading](/sdk/trading) — Place your first order * [SDK / Streaming](/sdk/streaming) — Subscribe to live market data and user events via WebSocket # Hyperliquid Source: https://public-perps-docs.mintlify.app/providers/hyperliquid Signing model, account abstraction, and configuration This page covers Hyperliquid-specific behavior that goes beyond the generic SDK documentation. For general concepts, see the [Concepts](/concepts/signing-modes) section. ## Deposits Deposits to Hyperliquid are made using the [LI.FI swap and bridge](https://docs.li.fi) functionality. USDC Spot and USDC Perps are available as Hyperliquid L1 protocol targets in LI.FI — deposits are routed directly to Hyperliquid Core, they do not need to go through Arbitrum. If the user already has USDC on Arbitrum, they can deposit directly via the [Hyperliquid bridge](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/bridge2#deposit) with a normal transfer. Funds can be deposited to either destination: | Deposit Destination | Description | | ------------------- | ------------------------------------------------- | | **USDC Spot** | Lands in the user's Hyperliquid spot balance | | **USDC Perps** | Lands directly in the user's perps margin balance | Under `unifiedAccount` abstraction mode (which the LI.FI Perps API automatically enables), both spot and perps USDC balances are available for perps trading across the default (`''`) and HIP-3 (`'xyz'`) sub-dexes. It does not matter which destination you deposit to — both are usable as margin. Deposit functionality is not part of the Perps API — use the existing [LI.FI SDK](https://docs.li.fi/integrate-li.fi-js-sdk/install-li.fi-sdk) or [API](https://docs.li.fi/li.fi-api/li.fi-api) to execute deposits. ## Withdrawals Withdrawals are submitted via a user-signed EIP-712 payload using the [SDK withdrawal flow](/sdk/withdrawal). Agents cannot initiate withdrawals — the user's wallet must sign directly regardless of signing mode. On Hyperliquid, withdrawals always send **USDC to the user's address on Arbitrum** via the Hyperliquid L1 → Arbitrum bridge. Processing typically takes 3–4 minutes. Under `unifiedAccount` abstraction mode, withdrawals are drawn from the user's **spot USDC** balance. Ensure sufficient spot USDC is available before initiating a withdrawal — perps margin must be freed (by closing positions or reducing margin) and transferred to spot first if needed. ## Signing Model ### Phantom Agent Signing Hyperliquid requires all order operations (place, cancel, trigger) to use an agent-based signing scheme at the protocol level. Even when the user signs directly, a temporary "phantom" agent is created per operation. This is a Hyperliquid protocol requirement, not an SDK limitation. ### USER Mode In `USER` mode, every trading operation requires the user's wallet to sign a phantom agent approval. In a browser context this means a wallet popup for each action; with an ethers/viem signer it means a direct signature per action. This makes `USER` mode functional but impractical for active trading on Hyperliquid. ```typescript theme={null} await perps.setSigningMode(userAddress, 'hyperliquid', 'USER'); // Every placeOrder, cancelOrder, and TP/SL requires a user signature ``` ### USER\_AGENT Mode (Recommended) `USER_AGENT` mode is strongly recommended for Hyperliquid. It requires a one-time agent approval, after which all trading operations are signed seamlessly by the SDK. ```typescript theme={null} await perps.setSigningMode(userAddress, 'hyperliquid', 'USER_AGENT'); // One-time agent approval, then no more user signatures for trading ``` The agent keypair is stored in the browser's `localStorage`, scoped per user + DEX pair. Approved agents are visible in the account's `config.agents` array (see [Account Configuration](#account-configuration) below). ### UX Comparison | Operation | USER mode | USER\_AGENT mode | | ---------------- | ------------------------------ | ----------------------- | | First-time setup | None | One-time agent approval | | Place order | User signature (phantom agent) | Signed by SDK | | Cancel order | User signature (phantom agent) | Signed by SDK | | TP/SL | User signature per trigger | Signed by SDK | | Withdrawal | User signature (always) | User signature (always) | See also: [Concepts / Signing Modes](/concepts/signing-modes), [SDK / Authorization](/sdk/authorization). ## Account Abstraction ### Account Types Hyperliquid supports multiple account types that determine how margin and balances are managed: | Account Type | Description | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `standard` | Isolated quote assets per DEX operator. Each HIP-3 DEX maintains a separate balance. | | `dexAbstraction` | Like-for-like quote assets are unified across HIP-3 DEXes. For example, the default DEX (`''`) and a HIP-3 DEX (`'xyz'`) both use USDC, so a single USDC perps balance covers both. | | `unifiedAccount` | Spot and perps balances are further unified per quote asset across like-asset HIP-3 DEXes. Spot USDC can be used for perps on both `''` and `'xyz'`. Same applies to other quote assets like USDH or USDN. | | `portfolioMargin` | Portfolio margin with cross-asset margining. | ### Backend Behavior The LI.FI Perps API automatically upgrades accounts from `standard` or `dexAbstraction` to `unifiedAccount` during authorization. `portfolioMargin` accounts are left as-is. This ensures all SDK users get the latest Hyperliquid account features (unified USDC balance). The upgrade is a one-way operation — it cannot be reverted. ### Account Configuration The `config` object returned by `getAccount()` contains Hyperliquid-specific account state: ```typescript theme={null} const account = await getAccount(client, { dex: 'hyperliquid', address: userAddress, }); console.log(account.config); // { // abstractionStatus: "unifiedAccount", // agents: [...], // builderFeeApproval: { // builderAddress: "0x...", // maxFeeRate: "0.001", // approved: true, // }, // } ``` | Config Key | Fields | Description | | -------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------- | | `abstractionStatus` | `string` | Current abstraction mode (`unifiedAccount`, `portfolioMargin`, `dexAbstraction`, or `null`) | | `agents` | `array` | Approved extra agents for this account | | `builderFeeApproval` | `builderAddress`, `maxFeeRate`, `approved` | Builder fee approval status (required for placing orders via LI.FI) | The SDK validates all config requirements automatically. These fields are useful for displaying account status in your UI. ## WebSocket Protocol Hyperliquid exposes a native WebSocket endpoint at `wss://api.hyperliquid.xyz/ws`. The SDK discovers this URL from the `wsUrl` field in the `GET /dexes` response. If you're using the SDK, see [Streaming](/sdk/streaming) instead. For the full WebSocket protocol specification (message format, keepalive, and subscription channels), see the [Hyperliquid WebSocket documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions). ### SDK Channel Mapping The SDK maps between normalized channel names and Hyperliquid-native channels: | SDK channel | HL subscription type | SDK event type | | -------------- | -------------------- | ------------------- | | `prices` | `allMids` | `PricesResponse` | | `orderbook` | `l2Book` | `OrderbookResponse` | | `trades` | `trades` | `HistoryItem[]` | | `candle` | `candle` | `Candle` | | `orderUpdates` | `orderUpdates` | `Order[]` | | `fills` | `userFills` | `HistoryItem[]` | | `positions` | `webData2` | `Position[]` | ### Rate Limits Hyperliquid enforces per-connection limits: * **1000** total subscriptions per WebSocket connection * **10** user-specific subscriptions per connection (orderUpdates, userFills, webData2) ## Additional Notes * Currently, only the default Hyperliquid venue and HIP-3 perps venues that use **USDC** as the quote asset are supported (e.g. `''`, `'xyz'`). * Only **isolated margin** is available for now. ## Related Pages * [Concepts](/concepts/signing-modes) — Signing modes, authorization flow, and the action pattern * [SDK / Authorization](/sdk/authorization) — Discover, build, sign, and submit DEX authorizations * [SDK / Streaming](/sdk/streaming) — Subscribe to live market data and user events via WebSocket * [SDK / Account](/sdk/account) — Fetch account balances, positions, and history * [SDK / Trading](/sdk/trading) — Place orders, cancel orders, and query order status * [SDK / Withdrawal](/sdk/withdrawal) — Build and submit withdrawals * [LI.FI Docs](https://docs.li.fi) — Cross-chain swap and bridge for deposits # Account Source: https://public-perps-docs.mintlify.app/sdk/account Fetch account balances, positions, open orders, and trade history Fetch account balances, positions, open orders, and trade history. The examples on this page use **Hyperliquid** (`dex: 'hyperliquid'`). Replace the `dex` value with any supported DEX from `getDexes()`. ## getAccount Returns the full account summary for a DEX including balances, positions, open orders, and fee tier. ```typescript theme={null} import { getAccount } from '@lifi/perps-sdk'; const account = await getAccount(client, { dex: 'hyperliquid', address: userAddress, }); console.log('Balance:', account.balances); console.log('Margin used:', account.marginUsed); console.log('Unrealized PnL:', account.unrealizedPnl); console.log('Positions:', account.positions.length); console.log('Open orders:', account.openOrders.length); ``` ### Parameters | Parameter | Type | Required | Description | | ----------------------------- | -------------------------------- | -------- | ------------------------------------- | | `client` | `PerpsSDKClient` | Yes | SDK client from `createPerpsClient()` | | `params.dex` | `string` | Yes | DEX identifier | | `params.address` | `string` | Yes | User's wallet address | | `options` | `SDKRequestOptions` | No | Request options | ### Returns `AccountResponse`: | Field | Type | Description | | ---------------------------- | -------------------------- | -------------------------------------------------------------------- | | `dex` | `string` | DEX identifier | | `address` | `string` | User's wallet address | | `balances` | `Balance[]` | Account balances by currency | | `marginUsed` | `string` | Margin currently in use | | `unrealizedPnl` | `string` | Total unrealized PnL | | `feeTier` | `FeeTier` | Maker and taker fee rates | | `positions` | `Position[]` | Open positions | | `openOrders` | `OpenOrder[]` | Resting orders | | `config` | `object` | DEX-specific account configuration (agent status, builder fee, etc.) | ### Position fields | Field | Type | Description | | ------------------------------- | ------------------------------------ | --------------------------- | | `symbol` | `string` | Trading symbol | | `assetId` | `number` | Asset ID | | `dex` | `string` | DEX identifier | | `side` | `'LONG' \| 'SHORT'` | Position direction | | `size` | `string` | Position size | | `entryPrice` | `string` | Average entry price | | `markPrice` | `string` | Current mark price | | `liquidationPrice` | `string` | Estimated liquidation price | | `unrealizedPnl` | `string` | Unrealized profit/loss | | `leverage` | `number` | Current leverage | | `marginUsed` | `string` | Margin allocated | | `marginMode` | `'ISOLATED' \| 'CROSS'` | Margin mode | ### OpenOrder fields | Field | Type | Description | | ------------------------- | ---------------------------------- | ------------------- | | `id` | `string` | Order ID | | `symbol` | `string` | Trading symbol | | `assetId` | `number` | Asset ID | | `dex` | `string` | DEX identifier | | `side` | `'BUY' \| 'SELL'` | Order direction | | `type` | `'LIMIT' \| 'MARKET'` | Order type | | `size` | `string` | Order size | | `price` | `string` | Limit price | | `filledSize` | `string` | Amount filled | | `reduceOnly` | `boolean` | Whether reduce-only | | `createdAt` | `string` | ISO 8601 timestamp | **API Reference:** [GET /account](/api-reference/account#get-account) *** ## getHistory Returns paginated historical orders. Results are sorted by creation time, newest first. ```typescript theme={null} import { getHistory } from '@lifi/perps-sdk'; const history = await getHistory(client, { dex: 'hyperliquid', address: userAddress, limit: 50, }); for (const item of history.items) { console.log(item.symbol, item.side, item.status, item.realizedPnl); } // Pagination if (history.pagination.hasMore) { const nextPage = await getHistory(client, { dex: 'hyperliquid', address: userAddress, cursor: history.pagination.cursor, }); } ``` ### Parameters | Parameter | Type | Required | Description | | ------------------------------- | -------------------------------- | -------- | ----------------------------------------- | | `client` | `PerpsSDKClient` | Yes | SDK client | | `params.dex` | `string` | Yes | DEX identifier | | `params.address` | `string` | Yes | User's wallet address | | `params.cursor` | `string` | No | Pagination cursor from previous response | | `params.limit` | `number` | No | Items per page (default 50, max 100) | | `params.startTime` | `number` | No | Filter: orders after this timestamp (ms) | | `params.endTime` | `number` | No | Filter: orders before this timestamp (ms) | | `options` | `SDKRequestOptions` | No | Request options | ### Returns `HistoryResponse`: | Field | Type | Description | | ------------------------- | ---------------------------- | ------------------- | | `dex` | `string` | DEX identifier | | `items` | `HistoryItem[]` | Historical orders | | `pagination` | `Pagination` | Pagination metadata | ### HistoryItem fields | Field | Type | Description | | -------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `id` | `string` | Order ID | | `symbol` | `string` | Trading symbol | | `assetId` | `number` | Asset ID | | `dex` | `string` | DEX identifier | | `side` | `'BUY' \| 'SELL'` | Order direction | | `type` | `'MARKET' \| 'LIMIT'` | Order type | | `size` | `string` | Original order size | | `price` | `string` | Order price | | `status` | `HistoryItemStatus` | `FILLED`, `PARTIALLY_FILLED`, `CANCELLED`, `REJECTED` (subset of `OrderStatus` — only terminal states appear in history) | | `filledSize` | `string?` | Amount filled (absent if no fills yet) | | `fee` | `string?` | Total fees paid (absent if no fills yet) | | `realizedPnl` | `string \| null?` | Realized PnL (present only when closing a position, `null` otherwise) | | `createdAt` | `string` | ISO 8601 timestamp | ### Pagination fields | Field | Type | Description | | ---------------------- | ---------------------- | -------------------------------------------------------------- | | `limit` | `number` | Items per page | | `hasMore` | `boolean` | Whether more pages exist — use this as the authoritative check | | `cursor` | `string?` | Cursor for next page (pass to `getHistory` for the next page) | | `nextUrl` | `string?` | Full URL for next page (may be absent) | **API Reference:** [GET /history](/api-reference/account#get-history) # Authorization Source: https://public-perps-docs.mintlify.app/sdk/authorization Discover, build, sign, and submit DEX authorizations Before trading on a DEX, the user must complete certain authorizations. The SDK provides methods to discover, build, sign, and submit these authorizations. The examples on this page use **Hyperliquid** (`dex: 'hyperliquid'`). Authorization keys like `ApproveAgent` and `ApproveBuilderFee` are Hyperliquid-specific — other DEXes will have different authorization requirements. Use `getDexes()` to discover available authorizations for each DEX. ## Overview The authorization flow has four steps: 1. **Discover** — `getDexes()` returns available authorizations per DEX 2. **Create** — `createAuthorization()` builds the payloads to sign 3. **Sign** — User signs each `typedData` with their wallet 4. **Submit** — `submitAuthorization()` sends the signed payloads ```mermaid theme={null} sequenceDiagram participant User participant SDK participant API as LI.FI Perps API participant DEX User->>SDK: authorize() SDK->>API: POST /createAuthorization API-->>SDK: actions[] SDK->>User: sign typedData User-->>SDK: signatures SDK->>API: POST /authorization API->>DEX: Submit to chain DEX-->>API: Result API-->>SDK: results[] SDK-->>User: done ``` If using `PerpsClient`, the `buildAuthorization()` method handles step 2 and automatically injects the agent address for `USER_AGENT` mode. ## Using PerpsClient (recommended) ### Step 1: Discover authorizations ```typescript theme={null} import { getDexes } from '@lifi/perps-sdk'; const { dexes } = await getDexes(client); const hl = dexes.find((d) => d.key === 'hyperliquid'); console.log(hl.authorizations); // [ // { key: 'ApproveAgent', name: 'Approve Agent', params: [{ name: 'agentAddress', ... }] }, // { key: 'ApproveBuilderFee', name: 'Approve Builder Fee', params: [] }, // ] ``` ### Step 2: Build authorization payloads ```typescript theme={null} const { actions } = await perps.buildAuthorization({ dex: 'hyperliquid', address: userAddress, authorizations: [ { key: 'ApproveAgent' }, // agentAddress auto-injected in USER_AGENT mode { key: 'ApproveBuilderFee' }, ], }); ``` The backend checks which authorizations are already active. Only unsatisfied authorizations are returned in `actions[]`. If all are already satisfied, `actions` will be empty. ### Step 3: Sign each action ```typescript theme={null} 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 4: Submit signed authorizations ```typescript theme={null} const { results } = await perps.submitAuthorizations({ dex: 'hyperliquid', address: userAddress, actions: signedActions, }); for (const result of results) { console.log(result.action, result.success ? 'OK' : result.error); } ``` Authorization submission returns `202 Accepted`. On-chain processing is asynchronous. Poll `getAccount()` to verify authorizations are active before placing orders. ## Using Service Functions (advanced) For more control, use the low-level service functions directly: ```typescript theme={null} import { createAuthorization, submitAuthorization } from '@lifi/perps-sdk'; // Build payloads const { actions } = await createAuthorization(client, { dex: 'hyperliquid', address: userAddress, authorizations: [ { key: 'ApproveAgent', params: { agentAddress: '0x...' } }, { key: 'ApproveBuilderFee' }, ], }); // Sign each action const signedActions = await Promise.all( actions.map(async (a) => ({ action: a.action, typedData: a.typedData, signature: await walletClient.signTypedData({ ...a.typedData }), })) ); // Submit const { results } = await submitAuthorization(client, { dex: 'hyperliquid', address: userAddress, actions: signedActions, }); for (const result of results) { console.log(result.action, result.success ? 'OK' : result.error); } ``` ## PerpsClient Methods ### buildAuthorization Build authorization payloads for the user to sign. ```typescript theme={null} const response = await perps.buildAuthorization(params); ``` **Parameters:** | Field | Type | Required | Description | | ----------------------------- | ----------------------------------- | -------- | ------------------------------------------------------------------------------------------ | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address (account owner) | | `signerAddress` | `string` | No | Address that will sign the payloads. Required for USER\_AGENT mode. Defaults to `address`. | | `authorizations` | `AuthorizationInput[]` | Yes | Authorizations to build | In `USER_AGENT` mode, the `PerpsClient` automatically sets `signerAddress` to the agent wallet address. In `USER` mode, the user signs directly so `signerAddress` is not needed. Each `AuthorizationInput`: | Field | Type | Required | Description | | --------------------- | -------------------------------------- | -------- | ------------------------------------------------- | | `key` | `string` | Yes | Authorization key from `dex.authorizations[].key` | | `params` | `Record` | No | Authorization-specific parameters | **Returns:** `{ actions: AuthorizationAction[] }` Each `AuthorizationAction`: | Field | Type | Description | | -------------------------- | ---------------------- | -------------------------- | | `action` | `string` | Authorization key | | `description` | `string?` | Human-readable description | | `typedData` | `object` | EIP-712 typed data to sign | ### submitAuthorizations Submit signed authorizations. ```typescript theme={null} await perps.submitAuthorizations(params); ``` **Parameters:** | Field | Type | Required | Description | | ---------------------------- | ------------------------------------ | -------- | --------------------------------------------------------------------------------------- | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address (account owner) | | `signerAddress` | `string` | No | Address that signed the payloads. Required for USER\_AGENT mode. Defaults to `address`. | | `actions` | `SignedAuthorization[]` | Yes | Signed authorization payloads | Each `SignedAuthorization`: | Field | Type | Required | Description | | ------------------------ | --------------------- | -------- | ---------------------------------------------- | | `action` | `string` | Yes | Authorization action type | | `typedData` | `object` | Yes | Original `typedData` from `buildAuthorization` | | `signature` | `string` | Yes | User's signature of the `typedData` | **Returns:** `AuthorizationsResponse` | Field | Type | Description | | ---------------------- | ------------------------------------ | --------------------------- | | `results` | `AuthorizationResult[]` | Result per submitted action | Each `AuthorizationResult`: | Field | Type | Description | | ---------------------- | ---------------------- | ----------------------------------- | | `action` | `string` | Authorization action key | | `success` | `boolean` | Whether the authorization succeeded | | `error` | `string?` | Error message if failed | ### getRequiredAuthorizations Determine which authorizations (if any) the user needs before trading. Sends the expected authorizations to the backend, which checks on-chain state and returns only those not yet satisfied. ```typescript theme={null} const required = await perps.getRequiredAuthorizations({ dex: 'hyperliquid', address: userAddress, }); if (required.isReady) { // No authorizations needed — ready to trade } else { // Typed data already included — sign directly const signedActions = await Promise.all( required.userAuthorizations.map(async (a) => ({ action: a.action, typedData: a.typedData, signature: await walletClient.signTypedData({ ...a.typedData }), })) ); // ... submit with executeAuthorizations ... } ``` **Parameters:** | Field | Type | Required | Description | | ---------------------- | --------------------- | -------- | --------------------- | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address | **Returns:** `RequiredAuthorizationsResult` | Field | Type | Description | | ---------------------------------- | ------------------------------------ | ----------------------------------------------------------- | | `userAuthorizations` | `AuthorizationAction[]` | Actions requiring user wallet signature (with typed data) | | `agentAuthorizations` | `AuthorizationAction[]` | Actions the SDK auto-signs with the agent (with typed data) | | `isReady` | `boolean` | Whether all authorizations are satisfied (ready to trade) | ### executeAuthorizations Submit user-signed actions, then auto-sign and submit any agent authorizations. ```typescript theme={null} const required = await perps.getRequiredAuthorizations({ dex: 'hyperliquid', address: userAddress, }); if (!required.isReady) { // Typed data already included — sign directly const signedActions = await Promise.all( required.userAuthorizations.map(async (a) => ({ action: a.action, typedData: a.typedData, signature: await walletClient.signTypedData({ ...a.typedData }), })) ); // Execute all authorizations (user + agent) const result = await perps.executeAuthorizations({ dex: 'hyperliquid', address: userAddress, required, userSignedActions: signedActions, }); console.log(result.userResults); // Results from user-signed submissions console.log(result.agentResults); // Results from agent-signed submissions (if any) } ``` **Parameters:** | Field | Type | Required | Description | | -------------------------------- | ------------------------------------------- | -------- | ----------------------------------------------------- | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address | | `required` | `RequiredAuthorizationsResult` | Yes | Result from `getRequiredAuthorizations()` | | `userSignedActions` | `SignedAuthorization[]` | Yes | User-signed actions for `required.userAuthorizations` | **Returns:** `ExecuteAuthorizationsResult` | Field | Type | Description | | --------------------------- | ------------------------------------- | ----------------------------------------------------------- | | `userResults` | `AuthorizationsResponse` | Results from user-signed authorization submission | | `agentResults` | `AuthorizationsResponse` | Results from agent-signed authorization submission (if any) | **API Reference:** [POST /createAuthorization](/api-reference/authorization#post-createauthorization), [POST /authorization](/api-reference/authorization#post-authorization) # Market Data Source: https://public-perps-docs.mintlify.app/sdk/market-data Fetch DEX information, markets, prices, OHLCV, and orderbooks Fetch DEX information, market details, prices, OHLCV chart data, and orderbook snapshots. All service functions require a `client` as the first argument. Create one with `createPerpsClient()`: ```typescript theme={null} import { createPerpsClient } from '@lifi/perps-sdk'; const client = createPerpsClient({ integrator: 'my-app' }); ``` The examples on this page use **Hyperliquid** (`dex: 'hyperliquid'`). Replace the `dex` value with any supported DEX from `getDexes()`. ## getDexes Returns all available perpetual DEX platforms, including their authorization requirements. ```typescript theme={null} import { getDexes } from '@lifi/perps-sdk'; const { dexes } = await getDexes(client); for (const dex of dexes) { console.log(dex.key, dex.name, dex.authorizations.length); } // hyperliquid Hyperliquid 2 ``` ### Parameters | Parameter | Type | Required | Description | | ---------------------- | -------------------------------- | -------- | ------------------------------------------------- | | `client` | `PerpsSDKClient` | Yes | SDK client from `createPerpsClient()` | | `options` | `SDKRequestOptions` | No | Request options (e.g., `signal` for cancellation) | ### Returns `DexesResponse` — `{ dexes: Dex[] }`: | Field | Type | Description | | -------------------- | -------------------- | -------------------- | | `dexes` | `Dex[]` | Array of DEX objects | Each `Dex`: | Field | Type | Description | | ----------------------------- | ------------------------------ | ------------------------------------------------------------------ | | `key` | `string` | Unique DEX identifier (used in query params) | | `name` | `string` | Display name | | `logoURI` | `string` | URL to DEX logo image | | `authorizations` | `Authorization[]` | Available authorizations for this DEX | | `wsUrl` | `string?` | WebSocket endpoint for streaming (see [Streaming](/sdk/streaming)) | | `extraData` | `object?` | DEX-specific extra data (sub-exchanges, feature flags, etc.) | Each `Authorization` object: | Field | Type | Description | | --------------------- | ----------------------------------- | ----------------------------------------------------------------------------- | | `key` | `string` | Authorization identifier (DEX-specific, e.g., `ApproveAgent` for Hyperliquid) | | `name` | `string` | Human-readable name | | `params` | `AuthorizationParam[]` | Required parameters | Use `dex.authorizations` to dynamically build the authorization UI. See [Authorization](/sdk/authorization) for the full flow. **API Reference:** [GET /dexes](/api-reference/market-data#get-dexes) *** ## getMarkets Returns all perpetual markets for a specified DEX with funding rates, open interest, and volume. ```typescript theme={null} import { getMarkets } from '@lifi/perps-sdk'; const { markets } = await getMarkets(client, { dex: 'hyperliquid' }); for (const market of markets) { console.log(market.symbol, market.markPrice, `${market.maxLeverage}x`); } // BTC 95000.50 50x // ETH 3200.25 50x ``` ### Parameters | Parameter | Type | Required | Description | | ------------------------- | -------------------------------- | -------- | --------------- | | `client` | `PerpsSDKClient` | Yes | SDK client | | `params.dex` | `string` | Yes | DEX identifier | | `options` | `SDKRequestOptions` | No | Request options | ### Returns `MarketsResponse` — `{ markets: Market[] }`: | Field | Type | Description | | ---------------------- | ----------------------- | ----------------------- | | `markets` | `Market[]` | Array of market objects | Each `Market`: | Field | Type | Description | | --------------------------- | -------------------------- | ------------------------------------------------------------- | | `symbol` | `string` | Trading symbol (e.g., `BTC`) | | `name` | `string` | Full asset name (e.g., `Bitcoin`) | | `logoURI` | `string` | URL to asset logo | | `assetId` | `number` | Asset ID (0-99 for main assets, 100000+ for xyz) | | `dex` | `string` | DEX identifier | | `szDecimals` | `number` | Size decimal places | | `maxLeverage` | `number` | Maximum allowed leverage | | `onlyIsolated` | `boolean` | Whether only isolated margin is supported | | `funding` | `FundingInfo` | Current funding rate and next funding time | | `openInterest` | `string?` | Total open interest in USD (may be absent for new markets) | | `volume24h` | `string?` | 24-hour trading volume in USD (may be absent for new markets) | | `markPrice` | `string` | Current mark price | **API Reference:** [GET /markets](/api-reference/market-data#get-markets) *** ## getMarket Returns details for a single market. ```typescript theme={null} import { getMarket } from '@lifi/perps-sdk'; const btc = await getMarket(client, { dex: 'hyperliquid', symbol: 'BTC' }); console.log(btc.markPrice, btc.funding.rate); // 95000.50 0.0001 ``` ### Parameters | Parameter | Type | Required | Description | | ---------------------------- | -------------------------------- | -------- | --------------------------- | | `client` | `PerpsSDKClient` | Yes | SDK client | | `params.dex` | `string` | Yes | DEX identifier | | `params.symbol` | `string` | Yes | Market symbol (e.g., `BTC`) | | `options` | `SDKRequestOptions` | No | Request options | ### Returns `Market` — Single market object (same shape as above). **API Reference:** [GET /markets/](/api-reference/market-data#get-marketssymbol) *** ## getPrices Returns current mid prices for all markets. Lightweight endpoint for frequent polling. ```typescript theme={null} import { getPrices } from '@lifi/perps-sdk'; const { prices } = await getPrices(client, { dex: 'hyperliquid' }); console.log(prices); // { BTC: '95000.50', ETH: '3200.25', SOL: '185.75' } ``` ### Parameters | Parameter | Type | Required | Description | | ----------------------------- | -------------------------------- | -------- | -------------------------- | | `client` | `PerpsSDKClient` | Yes | SDK client | | `params.dex` | `string` | Yes | DEX identifier | | `params.symbols` | `string[]` | No | Filter to specific symbols | | `options` | `SDKRequestOptions` | No | Request options | ### Returns `PricesResponse` — `{ prices: Record }`: | Field | Type | Description | | --------------------- | ------------------------------------- | -------------------------- | | `prices` | `Record` | Map of symbol to mid price | **API Reference:** [GET /prices](/api-reference/market-data#get-prices) *** ## getOhlcv Returns OHLCV candle data for charts. ```typescript theme={null} import { getOhlcv } from '@lifi/perps-sdk'; const { candles } = await getOhlcv(client, { dex: 'hyperliquid', symbol: 'BTC', interval: '1h', limit: 100, }); for (const candle of candles) { console.log(candle.t, candle.o, candle.h, candle.l, candle.c, candle.v); } ``` ### Parameters | Parameter | Type | Required | Description | | ------------------------------- | -------------------------------- | -------- | ------------------------------------------------------------------------------------------------------ | | `client` | `PerpsSDKClient` | Yes | SDK client | | `params.dex` | `string` | Yes | DEX identifier | | `params.symbol` | `string` | Yes | Market symbol | | `params.interval` | `string` | Yes | Candle interval: `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M` | | `params.startTime` | `number` | No | Start timestamp in milliseconds | | `params.endTime` | `number` | No | End timestamp in milliseconds | | `params.limit` | `number` | No | Max candles to return (default 100, max 1000) | | `options` | `SDKRequestOptions` | No | Request options | ### Returns `OhlcvResponse`: | Field | Type | Description | | ----------------------- | ----------------------- | -------------------- | | `dex` | `string` | DEX identifier | | `symbol` | `string` | Market symbol | | `interval` | `string` | Candle interval | | `candles` | `Candle[]` | Array of candle data | Each `Candle`: | Field | Type | Description | | ---------------- | --------------------- | ------------------------- | | `t` | `number` | Timestamp in milliseconds | | `o` | `string` | Open price | | `h` | `string` | High price | | `l` | `string` | Low price | | `c` | `string` | Close price | | `v` | `string` | Volume | **API Reference:** [GET /ohlcv/](/api-reference/market-data#get-ohlcvsymbol) *** ## getOrderbook Returns current orderbook snapshot with bids and asks. ```typescript theme={null} import { getOrderbook } from '@lifi/perps-sdk'; const book = await getOrderbook(client, { dex: 'hyperliquid', symbol: 'BTC', depth: 20, }); console.log('Best bid:', book.bids[0].price, book.bids[0].size); console.log('Best ask:', book.asks[0].price, book.asks[0].size); ``` ### Parameters | Parameter | Type | Required | Description | | ---------------------------- | -------------------------------- | -------- | -------------------------------------------- | | `client` | `PerpsSDKClient` | Yes | SDK client | | `params.dex` | `string` | Yes | DEX identifier | | `params.symbol` | `string` | Yes | Market symbol | | `params.depth` | `number` | No | Number of price levels (default 20, max 100) | | `options` | `SDKRequestOptions` | No | Request options | ### Returns `OrderbookResponse`: | Field | Type | Description | | ------------------------ | ------------------------------- | ---------------------------------- | | `dex` | `string` | DEX identifier | | `symbol` | `string` | Market symbol | | `bids` | `OrderbookLevel[]` | Bid price levels (descending) | | `asks` | `OrderbookLevel[]` | Ask price levels (ascending) | | `timestamp` | `number` | Snapshot timestamp in milliseconds | Each `OrderbookLevel`: | Field | Type | Description | | -------------------- | --------------------- | ------------------------ | | `price` | `string` | Price level | | `size` | `string` | Size at this price level | **API Reference:** [GET /orderbook/](/api-reference/market-data#get-orderbooksymbol) # Streaming Source: https://public-perps-docs.mintlify.app/sdk/streaming Subscribe to live price feeds, orderbook updates, fills, and order status via WebSocket The `PerpsWsClient` provides streaming data over WebSocket. Connections are made **directly to the DEX** for lowest latency — the SDK discovers the WebSocket URL from `GET /dexes` automatically. All incoming data is normalized to the same types used by REST responses, so your application logic works identically whether data comes from a REST call or a WebSocket event. ## Setup ```typescript theme={null} import { createPerpsClient, PerpsWsClient } from '@lifi/perps-sdk'; const client = createPerpsClient({ integrator: 'my-app' }); const ws = new PerpsWsClient(client); ``` The `PerpsWsClient` lazily initializes connections — no WebSocket is opened until you call `subscribe()`. ## Subscribe All channels use the same pattern: pass a subscription descriptor and a callback. The returned function unsubscribes when called. ```typescript theme={null} const unsub = await ws.subscribe( { channel: 'orderbook', dex: 'hyperliquid', symbol: 'ETH' }, (event) => { console.log('Best bid:', event.data.bids[0]?.price); console.log('Best ask:', event.data.asks[0]?.price); } ); // Later: stop receiving updates unsub(); ``` ## Available Channels ### prices All mid prices for every market on the DEX. Fires on every price change. ```typescript theme={null} await ws.subscribe( { channel: 'prices', dex: 'hyperliquid' }, (event) => { // event.data: PricesResponse — { prices: Record } console.log('BTC:', event.data.prices['BTC']); } ); ``` ### orderbook L2 orderbook for a specific market. Fires on every book update. ```typescript theme={null} await ws.subscribe( { channel: 'orderbook', dex: 'hyperliquid', symbol: 'BTC' }, (event) => { // event.data: OrderbookResponse — { bids, asks, timestamp, ... } const spread = Number(event.data.asks[0].price) - Number(event.data.bids[0].price); console.log('Spread:', spread); } ); ``` ### trades Public trade feed for a specific market. ```typescript theme={null} await ws.subscribe( { channel: 'trades', dex: 'hyperliquid', symbol: 'BTC' }, (event) => { // event.data: HistoryItem[] for (const trade of event.data) { console.log(trade.side, trade.size, '@', trade.price); } } ); ``` ### candle OHLCV candle updates for a specific market and interval. ```typescript theme={null} await ws.subscribe( { channel: 'candle', dex: 'hyperliquid', symbol: 'BTC', interval: '1h' }, (event) => { // event.data: Candle — { t, o, h, l, c, v } console.log('Close:', event.data.c); } ); ``` ### orderUpdates Live order status changes for a user's orders. Requires the user's wallet address. ```typescript theme={null} await ws.subscribe( { channel: 'orderUpdates', dex: 'hyperliquid', address: '0x...' }, (event) => { // event.data: Order[] for (const order of event.data) { console.log(order.orderId, order.status, order.filledSize); } } ); ``` ### fills Live fill notifications for a user's trades. ```typescript theme={null} await ws.subscribe( { channel: 'fills', dex: 'hyperliquid', address: '0x...' }, (event) => { // event.data: HistoryItem[] for (const fill of event.data) { console.log(fill.symbol, fill.side, fill.size, '@', fill.price); } } ); ``` ### positions Live position updates for a user. ```typescript theme={null} await ws.subscribe( { channel: 'positions', dex: 'hyperliquid', address: '0x...' }, (event) => { // event.data: Position[] for (const pos of event.data) { console.log(pos.symbol, pos.side, pos.size, 'PnL:', pos.unrealizedPnl); } } ); ``` ## Multiple Subscriptions You can subscribe to multiple channels simultaneously. Each returns an independent unsubscribe function. ```typescript theme={null} const unsubPrices = await ws.subscribe( { channel: 'prices', dex: 'hyperliquid' }, (event) => updatePriceDisplay(event.data.prices) ); const unsubBook = await ws.subscribe( { channel: 'orderbook', dex: 'hyperliquid', symbol: 'BTC' }, (event) => updateOrderbookDisplay(event.data) ); const unsubOrders = await ws.subscribe( { channel: 'orderUpdates', dex: 'hyperliquid', address: userAddress }, (event) => updateOrdersDisplay(event.data) ); // Unsubscribe individually unsubBook(); // Or close everything at once ws.close(); ``` ## Connection Lifecycle * **Lazy connection** — The WebSocket connects on the first `subscribe()` call for each DEX * **Automatic reconnection** — Exponential backoff reconnection on disconnect (150ms, 300ms, 600ms, up to 10s) * **Resubscription** — All active subscriptions are automatically re-sent after reconnection * **Keepalive** — 30-second ping/pong heartbeat to detect stale connections * **Refcounted** — Duplicate subscriptions to the same channel are deduplicated; the upstream subscription is removed only when the last listener unsubscribes ## Cleanup Always close the client when you're done to release WebSocket connections: ```typescript theme={null} // Close all connections and subscriptions ws.close(); ``` Individual subscriptions can be cleaned up by calling the returned unsubscribe function. The underlying WebSocket connection stays open as long as at least one subscription is active on that DEX. ## Type Safety The `subscribe` method is fully typed — the callback receives the correct event type based on the subscription channel: ```typescript theme={null} // TypeScript knows event.data is PricesResponse await ws.subscribe({ channel: 'prices', dex: 'hyperliquid' }, (event) => { event.data.prices; // Record — no cast needed }); // TypeScript knows event.data is OrderbookResponse await ws.subscribe({ channel: 'orderbook', dex: 'hyperliquid', symbol: 'BTC' }, (event) => { event.data.bids; // OrderbookLevel[] — no cast needed }); ``` # Trading Source: https://public-perps-docs.mintlify.app/sdk/trading Place orders, cancel orders, and query order status Place orders, cancel orders, and query order status. All trading operations follow the **create → sign → submit** pattern described in [Action Pattern](/concepts/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: ```typescript theme={null} 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: ```typescript theme={null} 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()`: ```typescript theme={null} 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. ```typescript theme={null} 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. ```typescript theme={null} 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` | ```typescript theme={null} // 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: ```typescript theme={null} 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 ```typescript theme={null} const result = await perps.cancelOrders({ dex: 'hyperliquid', address: userAddress, ids: ['12345678'], }); ``` ### USER mode (manual signing) ```typescript theme={null} 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 ```typescript theme={null} 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: ```typescript theme={null} 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](/error-codes) for the complete reference. ## PerpsClient Methods ### placeOrder Place an order (USER\_AGENT mode only — auto-signs and submits). ```typescript theme={null} 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`. ```typescript theme={null} 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). ```typescript theme={null} 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`. ```typescript theme={null} const { actions } = await perps.buildCancelOrder({ dex: 'hyperliquid', address: userAddress, ids: ['12345678'], }); ``` **Returns:** `CancelOrderPayloadResponse` with `actions[]` array. ### submitSignedOrder Submit pre-signed order payloads (USER mode). ```typescript theme={null} 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](/api-reference/trading#post-createorder), [POST /cancelOrder](/api-reference/trading#post-cancelorder), [POST /order](/api-reference/trading#post-order), [GET /order/](/api-reference/trading#get-orderid) *** ## End-to-End Example A complete example: wallet setup, authorization, and placing an order using `USER_AGENT` mode. ```typescript theme={null} 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' }] ``` # Withdrawal Source: https://public-perps-docs.mintlify.app/sdk/withdrawal Withdraw funds from a DEX perps account Withdraw funds from a DEX perps account. The withdrawal flow follows the same two-step create-then-submit pattern as authorization and trading. The examples on this page use **Hyperliquid** (`dex: 'hyperliquid'`). Withdrawal mechanics (supported assets, destination chains, and processing times) vary by DEX. ## Overview The withdrawal flow has three steps: 1. **Create** — `createWithdrawal()` builds the EIP-712 typed data payload 2. **Sign** — User signs the `typedData` with their wallet 3. **Submit** — `submitWithdrawal()` sends the signed payload Withdrawals are **user-signed only**. Agents cannot initiate withdrawals — this is a security feature. The withdrawal typed data requires the user's wallet signature regardless of signing mode. **Hyperliquid:** Withdrawal processing typically takes 3–4 minutes. The bridge transfer from Hyperliquid L1 to Arbitrum is asynchronous. ## Using PerpsClient (recommended) ### Step 1: Build withdrawal payload ```typescript theme={null} const { action } = await perps.buildWithdrawal({ dex: 'hyperliquid', address: userAddress, withdrawal: { destination: userAddress, // Address to receive withdrawn funds amount: '100.0', }, }); ``` ### Step 2: Sign the action ```typescript theme={null} const signedAction = { action: action.action, typedData: action.typedData, // Using viem walletClient: signature: await walletClient.signTypedData({ ...action.typedData }), // Or using a raw EIP-1193 provider: // signature: await provider.request({ // method: 'eth_signTypedData_v4', // params: [address, JSON.stringify(action.typedData)], // }), }; ``` ### Step 3: Submit signed withdrawal ```typescript theme={null} const { result } = await perps.submitWithdrawal({ dex: 'hyperliquid', address: userAddress, action: signedAction, }); if (result.success) { console.log('Withdrawal submitted successfully'); } else { console.error(`Withdrawal failed: ${result.error}`); } ``` Withdrawal submission returns `202 Accepted`. Processing is asynchronous — for Hyperliquid, this typically takes 3–4 minutes via the Arbitrum bridge. Poll `getAccount()` to verify the balance change. ## Using Service Functions (advanced) For more control, use the low-level service functions directly: ```typescript theme={null} import { createWithdrawal, submitWithdrawal } from '@lifi/perps-sdk'; // Build payload const { action } = await createWithdrawal(client, { dex: 'hyperliquid', address: userAddress, withdrawal: { destination: userAddress, amount: '100.0', }, }); // Sign the action const signedAction = { action: action.action, typedData: action.typedData, signature: await walletClient.signTypedData({ ...action.typedData }), }; // Submit const { result } = await submitWithdrawal(client, { dex: 'hyperliquid', address: userAddress, action: signedAction, }); console.log(result.action, result.success ? 'OK' : result.error); ``` ## PerpsClient Methods ### buildWithdrawal Build a withdrawal payload for the user to sign. ```typescript theme={null} const response = await perps.buildWithdrawal(params); ``` **Parameters:** | Field | Type | Required | Description | | ------------------------- | ------------------------------ | -------- | ------------------------------------- | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address (account owner) | | `withdrawal` | `WithdrawalInput` | Yes | Withdrawal details | `WithdrawalInput`: | Field | Type | Required | Description | | -------------------------- | --------------------- | -------- | -------------------------------------- | | `destination` | `string` | Yes | Destination address for the withdrawal | | `amount` | `string` | Yes | Amount to withdraw (e.g., `"100.0"`) | **Returns:** `{ action: WithdrawalAction }` | Field | Type | Description | | --------------------------------- | --------------------- | -------------------------- | | `action.action` | `string` | Action type (`"Withdraw"`) | | `action.description` | `string` | Human-readable description | | `action.typedData` | `object` | EIP-712 typed data to sign | ### submitWithdrawal Submit a signed withdrawal. ```typescript theme={null} const response = await perps.submitWithdrawal(params); ``` **Parameters:** | Field | Type | Required | Description | | ---------------------- | ------------------------------- | -------- | ------------------------------------- | | `dex` | `string` | Yes | DEX identifier | | `address` | `string` | Yes | User's wallet address (account owner) | | `action` | `SignedWithdrawal` | Yes | Signed withdrawal payload | `SignedWithdrawal`: | Field | Type | Required | Description | | ------------------------ | --------------------- | -------- | ------------------------------------------- | | `action` | `string` | Yes | Action type from `buildWithdrawal` | | `typedData` | `object` | Yes | Original `typedData` from `buildWithdrawal` | | `signature` | `string` | Yes | User's signature of the `typedData` | **Returns:** `{ result: WithdrawalResult }` | Field | Type | Description | | ----------------------------- | ---------------------- | ----------------------------------- | | `result.action` | `string` | Action type | | `result.success` | `boolean` | Whether the withdrawal was accepted | | `result.error` | `string` | Error message (if failed) | **API Reference:** [POST /createWithdrawal](/api-reference/withdrawal#post-createwithdrawal), [POST /withdrawal](/api-reference/withdrawal#post-withdrawal)