Get Positions (WebSocket)
Retrieve position information for the authenticated subaccount through the WebSocket connection with comprehensive filtering, sorting, and pagination capabilities, providing access to positions in any status (open, close, update) with flexible query options.
Request-Response vs Subscriptions
This method provides on-demand snapshots of current positions. For real-time updates when positions change, use SubAccount Updates Subscription.
| Method | Purpose | When to Use |
|---|---|---|
getPositions | Comprehensive position querying | Initial load, position history, filtered searches, reconciliation |
| User Updates Subscription | Real-time notifications | Live PnL updates, liquidation alerts |
Endpoint
ws.send() wss://api.synthetix.io/v1/ws/tradeRequest
Request Format
{
"id": "getpositions-1",
"method": "post",
"params": {
"action": "getPositions",
"status": ["open", "close"],
"symbol": "BTC-USDT",
"fromTime": 1704067200000,
"toTime": 1704153600000,
"limit": 50,
"offset": 0,
"sortBy": "updatedAt",
"sortOrder": "desc",
"subAccountId": "1867542890123456789",
"nonce": 1704067200000,
"signature": {
"v": 28,
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
}
}
}EIP-712 Domain:
{ "name": "Synthetix", "version": "1", "chainId": 1, "verifyingContract": "0x0000000000000000000000000000000000000000" }
Parameters
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Client-generated unique request identifier |
method | string | Yes | Must be "post" |
params | object | Yes | Contains all parameters for the request |
Params Object
| Parameter | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "getPositions" |
status | string[] | No | Filter by position status: ["open", "close", "update"] |
symbol | string | No | Filter positions by specific market symbol |
fromTime | integer | No | Start timestamp in milliseconds (inclusive) |
toTime | integer | No | End timestamp in milliseconds (inclusive) |
limit | integer | No | Maximum number of positions to return (default: 50, max: 1000) |
offset | integer | No | Number of positions to skip for pagination (default: 0) |
sortBy | string | No | Sort field: "createdAt", "updatedAt" (default: "updatedAt") |
sortOrder | string | No | Sort direction: "asc" or "desc" (default: "desc") |
subAccountId | string | Yes | Subaccount identifier |
nonce | integer | Yes | Incrementing nonce (Unix ms timestamp as number) |
signature | object | Yes | EIP-712 signature |
Filter Combinations
The endpoint supports powerful filter combinations:
- All Positions: Omit
statusto get positions in any status - Open Positions Only:
"status": ["open"] - Historical Positions:
"status": ["close"]with time range - Recent Activity: Use
fromTimewithouttoTimefor positions since a specific time - Symbol-Specific: Combine
symbolwith any other filters
Response Format
Response Structure
| Field | Type | Description |
|---|---|---|
type | string | Always "response" |
responseType | string | Always "position" for position query responses |
result | object | Contains position data (omitted when error occurs) |
result.subAccountId | string | Subaccount identifier |
result.positions | array | Array of position objects matching filter criteria |
error | object | Error details (only present when an error occurs) |
requestId | string | Request tracking identifier |
timestamp | integer | Unix milliseconds timestamp |
Success Response Examples
Multiple Positions (Open and Closed)
{
"id": "getpositions-1",
"status": 200,
"result": [
{
"positionId": "pos_12345",
"subAccountId": "1867542890123456789",
"symbol": "ETH-USDT",
"side": "long",
"quantity": "2.5",
"entryPrice": "2400.00",
"realizedPnl": "50.00",
"unrealizedPnl": "125.00",
"usedMargin": "612.50",
"maintenanceMargin": "306.25",
"liquidationPrice": "2160.00",
"status": "open",
"netFunding": "15.50",
"takeProfitOrderIds": ["tp_001"],
"stopLossOrderIds": ["sl_002"],
"createdAt": 1735680000000,
"updatedAt": 1735689900000
},
{
"positionId": "pos_12346",
"subAccountId": "1867542890123456789",
"symbol": "BTC-USDT",
"side": "short",
"quantity": "0.1",
"entryPrice": "42000.00",
"realizedPnl": "200.00",
"unrealizedPnl": "0.00",
"usedMargin": "0.00",
"maintenanceMargin": "0.00",
"liquidationPrice": "0.00",
"status": "close",
"netFunding": "-8.50",
"takeProfitOrderIds": [],
"stopLossOrderIds": [],
"createdAt": 1735685000000,
"updatedAt": 1735689800000
}
]
}Open Positions Only
{
"id": "getpositions-2",
"status": 200,
"result": [
{
"positionId": "pos_12347",
"subAccountId": "1867542890123456789",
"symbol": "BTC-USDT",
"side": "long",
"quantity": "0.2",
"entryPrice": "43500.00",
"realizedPnl": "0.00",
"unrealizedPnl": "100.00",
"usedMargin": "1760.00",
"maintenanceMargin": "880.00",
"liquidationPrice": "41000.00",
"status": "open",
"netFunding": "2.25",
"takeProfitOrderIds": ["tp_003"],
"stopLossOrderIds": ["sl_004"],
"createdAt": 1735689000000,
"updatedAt": 1735689900000
}
]
}Empty Result Set
{
"id": "getpositions-3",
"status": 200,
"result": []
}Position Status Types
| Status | Description | Characteristics |
|---|---|---|
open | Active position | Has unrealized PnL, margin requirements, liquidation price |
close | Closed position | Only realized PnL, no margin requirements |
update | Position being updated | Transitional state during modifications |
Error Response
{
"id": "getpositions-1",
"status": 400,
"result": null,
"error": {
"code": 400,
"message": "Invalid time range: fromTime must be less than or equal to toTime"
}
}Implementation Example
class PositionQuery {
constructor(ws, signer) {
this.ws = ws;
this.signer = signer;
this.requestId = 0;
}
async getPositions(options = {}) {
const {
status = [],
symbol = null,
fromTime = null,
toTime = null,
limit = 50,
offset = 0,
sortBy = "updatedAt",
sortOrder = "desc",
subAccountId
} = options;
// Generate nonce
const nonce = Date.now();
// Create query signature
const signature = await this.signGetPositions(options, nonce);
// Build action object
const action = {
type: "getPositions",
limit,
offset,
sortBy,
sortOrder
};
if (status.length > 0) {
action.status = status;
}
if (symbol) {
action.symbol = symbol;
}
if (fromTime) {
action.fromTime = fromTime;
}
if (toTime) {
action.toTime = toTime;
}
// Build request
const request = {
method: "getPositions",
params: {
action,
subAccountId,
nonce,
signature
}
};
// Send and wait for response
return this.sendRequest(request);
}
async signGetPositions(options, nonce) {
const domain = {
name: "Synthetix",
version: "1",
chainId: 1,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
const types = {
GetPositions: [
{ name: "action", type: "GetPositionsAction" },
{ name: "subAccountId", type: "string" },
{ name: "nonce", type: "uint256" }
],
GetPositionsAction: [
{ name: "action", type: "string" },
{ name: "status", type: "string" }, // Note: stringified array
{ name: "symbol", type: "string" },
{ name: "fromTime", type: "uint256" },
{ name: "toTime", type: "uint256" },
{ name: "limit", type: "uint256" },
{ name: "offset", type: "uint256" },
{ name: "sortBy", type: "string" },
{ name: "sortOrder", type: "string" }
]
};
const message = {
action: {
type: "getPositions",
status: JSON.stringify(options.status || []),
symbol: options.symbol || "",
fromTime: options.fromTime || 0,
toTime: options.toTime || 0,
limit: options.limit || 50,
offset: options.offset || 0,
sortBy: options.sortBy || "updatedAt",
sortOrder: options.sortOrder || "desc"
},
subAccountId: options.subAccountId,
nonce
};
const signature = await this.signer._signTypedData(domain, types, message);
return ethers.utils.splitSignature(signature);
}
sendRequest(request) {
return new Promise((resolve, reject) => {
this.pendingRequests.set(request.requestId, { resolve, reject });
this.ws.send(JSON.stringify(request));
setTimeout(() => {
if (this.pendingRequests.has(request.requestId)) {
this.pendingRequests.delete(request.requestId);
reject(new Error('Request timeout'));
}
}, 10000); // 10 second timeout
});
}
}
// Usage Examples
async function getOpenPositions(query, subAccountId) {
try {
const result = await query.getPositions({
subAccountId,
status: ["open"]
});
console.log(`Found ${result.positions.length} open positions`);
return result;
} catch (error) {
console.error('Failed to get open positions:', error);
throw error;
}
}
async function getPositionHistory(query, subAccountId, fromTime) {
try {
const result = await query.getPositions({
subAccountId,
status: ["close"],
fromTime,
sortBy: "updatedAt",
sortOrder: "desc"
});
console.log(`Found ${result.positions.length} historical positions`);
return result;
} catch (error) {
console.error('Failed to get position history:', error);
throw error;
}
}
async function getPositionBySymbol(query, subAccountId, symbol) {
try {
const result = await query.getPositions({
subAccountId,
symbol,
status: ["open"]
});
if (result.positions.length > 0) {
const position = result.positions[0];
console.log(`${symbol} position: ${position.quantity} @ ${position.entryPrice}`);
return position;
} else {
console.log(`No open position for ${symbol}`);
return null;
}
} catch (error) {
console.error(`Failed to get position for ${symbol}:`, error);
throw error;
}
}Implementation Notes
- Position queries must include valid subaccount authentication
- Authentication timestamps must be monotonically increasing per subaccount
- EIP-712 domain separator must use exactly "Synthetix" as domain name (not "Synthetix Offchain" or other variants)
- Time range filters must specify valid Unix millisecond timestamps
Position Object Structure
class PositionMonitor {
constructor(query, subAccountId) {
this.query = query;
this.subAccountId = subAccountId;
this.positions = new Map();
this.callbacks = new Map();
this.monitoring = false;
this.riskAlerts = new Map();
}
async startMonitoring(interval = 1000) {
this.monitoring = true;
// Initial fetch
await this.refreshPositions();
// Set up periodic refresh
this.monitoringInterval = setInterval(async () => {
if (this.monitoring) {
await this.refreshPositions();
}
}, interval);
}
stopMonitoring() {
this.monitoring = false;
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
}
}
async refreshPositions() {
try {
const result = await this.query.getOpenPositions({
subAccountId: this.subAccountId
});
const newPositions = new Map();
const changes = [];
// Process current positions
for (const position of result.positions) {
const symbol = position.symbol;
const previousPosition = this.positions.get(symbol);
newPositions.set(symbol, position);
if (!previousPosition) {
// New position
changes.push({
type: 'opened',
symbol,
position
});
} else if (this.hasPositionChanged(previousPosition, position)) {
// Position updated
changes.push({
type: 'updated',
symbol,
previousPosition,
position,
changes: this.getPositionChanges(previousPosition, position)
});
}
}
// Find closed positions
for (const [symbol, position] of this.positions) {
if (!newPositions.has(symbol)) {
changes.push({
type: 'closed',
symbol,
position
});
}
}
// Update stored positions
this.positions = newPositions;
// Check risk alerts
this.checkRiskAlerts(result);
// Notify callbacks of changes
for (const change of changes) {
this.notifyCallbacks(change);
}
} catch (error) {
console.error('Position monitoring refresh failed:', error);
}
}
hasPositionChanged(previous, current) {
return previous.size !== current.size ||
previous.unrealizedPnl !== current.unrealizedPnl ||
previous.markPrice !== current.markPrice ||
previous.marginRatio !== current.marginRatio;
}
getPositionChanges(previous, current) {
const changes = [];
if (previous.size !== current.size) {
changes.push({
field: 'size',
from: previous.size,
to: current.size,
delta: parseFloat(current.size) - parseFloat(previous.size)
});
}
if (previous.unrealizedPnl !== current.unrealizedPnl) {
changes.push({
field: 'unrealizedPnl',
from: previous.unrealizedPnl,
to: current.unrealizedPnl,
delta: parseFloat(current.unrealizedPnl) - parseFloat(previous.unrealizedPnl)
});
}
if (previous.markPrice !== current.markPrice) {
changes.push({
field: 'markPrice',
from: previous.markPrice,
to: current.markPrice,
delta: parseFloat(current.markPrice) - parseFloat(previous.markPrice)
});
}
return changes;
}
onPositionChange(callback) {
const id = Date.now() + Math.random();
this.callbacks.set(id, callback);
return () => this.callbacks.delete(id);
}
notifyCallbacks(change) {
for (const callback of this.callbacks.values()) {
try {
callback(change);
} catch (error) {
console.error('Position change callback error:', error);
}
}
}
// Risk management
setRiskAlert(symbol, alertConfig) {
this.riskAlerts.set(symbol, alertConfig);
}
checkRiskAlerts(result) {
for (const position of result.positions) {
const alert = this.riskAlerts.get(position.symbol);
if (!alert) continue;
const marginRatio = parseFloat(position.marginRatio);
const pnlPct = parseFloat(position.unrealizedPnl) / parseFloat(position.margin);
// Check margin ratio alert
if (alert.marginRatioThreshold && marginRatio >= alert.marginRatioThreshold) {
this.triggerAlert('marginRatio', position, { marginRatio, threshold: alert.marginRatioThreshold });
}
// Check PnL percentage alert
if (alert.pnlThreshold && pnlPct <= alert.pnlThreshold) {
this.triggerAlert('pnlThreshold', position, { pnlPct, threshold: alert.pnlThreshold });
}
// Check liquidation distance
const markPrice = parseFloat(position.markPrice);
const liqPrice = parseFloat(position.liquidationPrice);
const liqDistance = Math.abs(markPrice - liqPrice) / markPrice;
if (alert.liquidationDistance && liqDistance <= alert.liquidationDistance) {
this.triggerAlert('liquidationRisk', position, { liqDistance, threshold: alert.liquidationDistance });
}
}
}
triggerAlert(type, position, data) {
const alert = {
type,
symbol: position.symbol,
position,
data,
timestamp: Date.now()
};
console.warn(`🚨 RISK ALERT [${type}] for ${position.symbol}:`, data);
// Notify callbacks with alert
this.notifyCallbacks({
type: 'alert',
alert
});
}
}
// Usage
const monitor = new PositionMonitor(query, subAccountId);
// Set up risk alerts
monitor.setRiskAlert('BTC-USDT', {
marginRatioThreshold: 0.8, // 80% margin ratio
pnlThreshold: -0.1, // -10% PnL
liquidationDistance: 0.05 // 5% from liquidation
});
// Listen for position changes
const unsubscribe = monitor.onPositionChange((change) => {
switch (change.type) {
case 'opened':
console.log('Position opened:', change.position);
break;
case 'updated':
console.log('Position updated:', change.symbol);
for (const fieldChange of change.changes) {
console.log(` ${fieldChange.field}: ${fieldChange.from} → ${fieldChange.to}`);
}
break;
case 'closed':
console.log('Position closed:', change.symbol);
break;
case 'alert':
console.log('Risk alert:', change.alert);
break;
}
});
await monitor.startMonitoring();Portfolio Analytics
class PositionAnalytics {
constructor(query, subAccountId) {
this.query = query;
this.subAccountId = subAccountId;
}
async getPortfolioSummary() {
const result = await this.query.getOpenPositions({
subAccountId: this.subAccountId
});
const positions = result.positions;
const summary = {
totalPositions: positions.length,
longPositions: 0,
shortPositions: 0,
totalNotional: 0,
totalMargin: 0,
totalUnrealizedPnl: 0,
avgLeverage: 0,
marginUtilization: 0,
riskMetrics: {
maxSinglePositionRisk: 0,
correlationRisk: 'low', // simplified
liquidationRisk: 'low'
}
};
let totalLeverageWeighted = 0;
let maxPositionSize = 0;
let closeToLiquidation = 0;
for (const position of positions) {
if (position.side === 'long') {
summary.longPositions++;
} else {
summary.shortPositions++;
}
const notional = Math.abs(parseFloat(position.size)) * parseFloat(position.markPrice);
const margin = parseFloat(position.margin);
const pnl = parseFloat(position.unrealizedPnl);
const leverage = parseFloat(position.leverage);
summary.totalNotional += notional;
summary.totalMargin += margin;
summary.totalUnrealizedPnl += pnl;
totalLeverageWeighted += leverage * margin;
// Risk metrics
if (notional > maxPositionSize) {
maxPositionSize = notional;
}
// Check liquidation risk
const markPrice = parseFloat(position.markPrice);
const liqPrice = parseFloat(position.liquidationPrice);
const liqDistance = Math.abs(markPrice - liqPrice) / markPrice;
if (liqDistance < 0.1) { // Within 10% of liquidation
closeToLiquidation++;
}
}
if (summary.totalMargin > 0) {
summary.avgLeverage = totalLeverageWeighted / summary.totalMargin;
summary.marginUtilization = summary.totalMargin / parseFloat(result.summary.accountEquity);
}
summary.riskMetrics.maxSinglePositionRisk = maxPositionSize / summary.totalNotional;
summary.riskMetrics.liquidationRisk = closeToLiquidation > 0 ? 'high' : 'low';
return summary;
}
async getPositionMetrics() {
const result = await this.query.getOpenPositions({
subAccountId: this.subAccountId
});
return result.positions.map(position => {
const size = parseFloat(position.size);
const markPrice = parseFloat(position.markPrice);
const entryPrice = parseFloat(position.entryPrice);
const margin = parseFloat(position.margin);
const unrealizedPnl = parseFloat(position.unrealizedPnl);
const liqPrice = parseFloat(position.liquidationPrice);
const notional = Math.abs(size) * markPrice;
const pnlPct = (unrealizedPnl / margin) * 100;
const priceMovePct = ((markPrice - entryPrice) / entryPrice) * 100;
const liqDistance = Math.abs(markPrice - liqPrice) / markPrice * 100;
return {
symbol: position.symbol,
side: position.side,
size,
notional,
pnlPct,
priceMovePct,
liquidationDistance: liqDistance,
marginRatio: parseFloat(position.marginRatio) * 100,
leverage: parseFloat(position.leverage),
riskLevel: this.calculateRiskLevel(position)
};
});
}
calculateRiskLevel(position) {
const marginRatio = parseFloat(position.marginRatio);
const markPrice = parseFloat(position.markPrice);
const liqPrice = parseFloat(position.liquidationPrice);
const liqDistance = Math.abs(markPrice - liqPrice) / markPrice;
if (marginRatio > 0.8 || liqDistance < 0.05) {
return 'high';
} else if (marginRatio > 0.6 || liqDistance < 0.1) {
return 'medium';
} else {
return 'low';
}
}
async getDiversificationMetrics() {
const result = await this.query.getOpenPositions({
subAccountId: this.subAccountId
});
const assetExposure = new Map();
let totalNotional = 0;
for (const position of result.positions) {
const asset = position.symbol.split('-')[0]; // Extract base asset
const notional = Math.abs(parseFloat(position.size)) * parseFloat(position.markPrice);
totalNotional += notional;
const currentExposure = assetExposure.get(asset) || 0;
assetExposure.set(asset, currentExposure + notional);
}
// Calculate concentration ratios
const exposureArray = Array.from(assetExposure.values()).sort((a, b) => b - a);
const concentrationRatios = {
top1: totalNotional > 0 ? exposureArray[0] / totalNotional : 0,
top3: totalNotional > 0 ? exposureArray.slice(0, 3).reduce((sum, val) => sum + val, 0) / totalNotional : 0,
herfindahl: totalNotional > 0 ? exposureArray.reduce((sum, val) => sum + Math.pow(val / totalNotional, 2), 0) : 0
};
return {
assetExposure: Object.fromEntries(assetExposure),
concentrationRatios,
diversificationScore: 1 - concentrationRatios.herfindahl // Higher = more diversified
};
}
}Position Risk Manager
class PositionRiskManager {
constructor(query, subAccountId) {
this.query = query;
this.subAccountId = subAccountId;
this.riskLimits = new Map();
}
setRiskLimits(limits) {
// limits = {
// maxPositionSize: 100000,
// maxLeverage: 20,
// maxMarginUtilization: 0.8,
// maxSingleAssetExposure: 0.3
// }
this.riskLimits = new Map(Object.entries(limits));
}
async checkRiskCompliance() {
const result = await this.query.getOpenPositions({
subAccountId: this.subAccountId
});
const violations = [];
const warnings = [];
// Check individual position limits
for (const position of result.positions) {
const notional = Math.abs(parseFloat(position.size)) * parseFloat(position.markPrice);
const leverage = parseFloat(position.leverage);
const marginRatio = parseFloat(position.marginRatio);
// Max position size
const maxSize = this.riskLimits.get('maxPositionSize');
if (maxSize && notional > maxSize) {
violations.push({
type: 'positionSize',
symbol: position.symbol,
value: notional,
limit: maxSize,
severity: 'high'
});
}
// Max leverage
const maxLev = this.riskLimits.get('maxLeverage');
if (maxLev && leverage > maxLev) {
violations.push({
type: 'leverage',
symbol: position.symbol,
value: leverage,
limit: maxLev,
severity: 'medium'
});
}
// Margin ratio warning
if (marginRatio > 0.7) {
warnings.push({
type: 'marginRatio',
symbol: position.symbol,
value: marginRatio,
threshold: 0.7,
severity: marginRatio > 0.8 ? 'high' : 'medium'
});
}
}
// Portfolio-level checks
const portfolioMetrics = await this.calculatePortfolioRisk(result);
const maxMarginUtil = this.riskLimits.get('maxMarginUtilization');
if (maxMarginUtil && portfolioMetrics.marginUtilization > maxMarginUtil) {
violations.push({
type: 'marginUtilization',
value: portfolioMetrics.marginUtilization,
limit: maxMarginUtil,
severity: 'high'
});
}
return {
compliant: violations.length === 0,
violations,
warnings,
portfolioMetrics
};
}
async calculatePortfolioRisk(result) {
const positions = result.positions;
const summary = result.summary;
const totalNotional = positions.reduce((sum, pos) => {
return sum + Math.abs(parseFloat(pos.size)) * parseFloat(pos.markPrice);
}, 0);
const marginUtilization = parseFloat(summary.totalMargin) / parseFloat(summary.accountEquity);
return {
totalNotional,
marginUtilization,
totalUnrealizedPnl: parseFloat(summary.totalUnrealizedPnl),
accountEquity: parseFloat(summary.accountEquity),
freeCollateral: parseFloat(summary.freeCollateral)
};
}
async generateRiskReport() {
const compliance = await this.checkRiskCompliance();
const analytics = new PositionAnalytics(this.query, this.subAccountId);
const diversification = await analytics.getDiversificationMetrics();
const positionMetrics = await analytics.getPositionMetrics();
return {
timestamp: Date.now(),
compliance,
diversification,
positions: positionMetrics,
riskScore: this.calculateOverallRiskScore(compliance, diversification, positionMetrics)
};
}
calculateOverallRiskScore(compliance, diversification, positions) {
let score = 100; // Start with perfect score
// Deduct for violations
score -= compliance.violations.length * 15;
score -= compliance.warnings.length * 5;
// Deduct for concentration
score -= (1 - diversification.diversificationScore) * 20;
// Deduct for high-risk positions
const highRiskPositions = positions.filter(p => p.riskLevel === 'high').length;
score -= highRiskPositions * 10;
return Math.max(0, Math.min(100, score));
}
}Position Object Structure
Position Fields
Important: All numeric fields in position objects are represented as strings to ensure decimal precision. Timestamps are provided as strings containing Unix milliseconds.
| Field | Type | Description |
|---|---|---|
positionId | string | Unique position identifier |
subAccountId | string | Subaccount identifier |
symbol | string | Market symbol |
side | string | Position side ("long" or "short") |
quantity | string | Position size (always positive) |
entryPrice | string | Volume-weighted average entry price |
markPrice | string | Current mark price |
notionalValue | string | Position notional value (quantity × mark price) |
usedMargin | string | Margin currently allocated to position |
maintenanceMargin | string | Minimum margin for position maintenance |
unrealizedPnl | string | Current unrealized profit/loss |
leverage | string | Position leverage |
liquidationPrice | string | Price at which position will be liquidated |
status | string | Position status ("open", "close", "update") |
netFunding | string | Net funding payments received/paid |
takeProfitOrderIds | string[] | Associated take profit order IDs |
stopLossOrderIds | string[] | Associated stop loss order IDs |
createdAt | string | Position creation timestamp (Unix ms) |
updatedAt | string | Last update timestamp (Unix ms) |
Signing
All trading methods are signed using EIP-712. Each successful trading request will contain:
- A piece of structured data that includes the sender address
- A signature of the hash of that structured data, signed by the sender
For detailed information on EIP-712 signing, see EIP-712 Signing.
Nonce Management
The nonce system prevents replay attacks and ensures order uniqueness:
- Use current timestamp in milliseconds as nonce
- Each nonce must be greater than the previous one
- Recommended: Use
Date.now()or equivalent - If nonce conflicts occur, increment by 1 and retry
| Error | Description |
|---|---|
| Invalid signature | EIP-712 signature validation failed |
| Invalid market symbol | Market symbol not recognized |
| Nonce already used | Nonce must be greater than previous value |
| Rate limit exceeded | Too many requests in time window |
| Request expired | expiresAfter timestamp has passed |
Next Steps
- SubAccount Updates Subscription - Real-time position change notifications
- Get Orders - Order querying via WebSocket
- Cancel All Orders - Emergency position management
- REST Unified Endpoint - Recommended unified approach with historical data support