Events
Every event is emitted by the Diamond (0xea1b8E4aB7f14F7dCA68c5B214303B13078FC5ec), so a single address filter captures the whole protocol.
Because opens and closes are two-phase, the events fall into two groups: request events from the trader's own transaction, and settlement events from the keeper's transaction. An integration that needs to know the real outcome must watch the settlement events.
Opening
| Event | Phase | Meaning |
|---|---|---|
MarketPendingTrade | request | Market open accepted, collateral locked |
OpenPosition | settlement | A new position slot was created |
PositionIncreased | settlement | An existing slot grew, entry price blended |
PendingTradeRefund | settlement | Open failed; collateral returned |
SetOpenTradeEarliestCloseTime | settlement | Minimum holding period recorded |
ExtraFeeCharged | settlement | extraFee paid to the broker's receiver |
event MarketPendingTrade(address indexed user, bytes32 indexed tradeHash, OpenDataInput trade);
event OpenPosition(
address indexed user,
bytes32 indexed positionHash,
bytes32 indexed sourceHash,
PositionOpenUpdate update
);
event PositionIncreased(
address indexed user,
bytes32 indexed positionHash,
bytes32 indexed sourceHash,
PositionOpenUpdate update
);
event PendingTradeRefund(address indexed user, bytes32 indexed tradeHash, Refund refund);
event ExtraFeeCharged(
uint24 indexed brokerId,
address token, // lvToken — extraFee is converted before payout
uint96 amount, // lvToken decimals
address user,
bytes32 tradeHash
);sourceHash links back to the request: the pending trade hash for a market order, or the order hash for a limit order.
enum PositionOpenSource { MARKET, LIMIT }
struct PositionOpenUpdate {
PositionOpenSource source;
bytes32 sourceHash;
uint128 addedQty; // 1e10
uint128 addedEntryPrice; // 1e18, the fill price of this leg
OpenTradeEventV2 position; // the position after the update
}WARNING
There is no OpenMarketTrade event. If you are migrating from an older integration, replace it with OpenPosition / PositionIncreased.
Closing
| Event | Phase | Meaning |
|---|---|---|
CloseTradeRequested | request | Close accepted |
ClosePosition | settlement | Position fully closed, slot removed |
PositionDecreased | settlement | Partial close settled, remainder open |
ExecuteDecreaseOrderSuccessful | settlement | A TP/SL order fired |
ExecuteCloseRejected | settlement | Close could not be settled |
CloseTradeReceived | settlement | Payout transferred to the trader |
event CloseTradeRequested(
address indexed user,
bytes32 indexed positionHash,
bytes32 indexed closeHash,
bytes32 requestId,
uint128 closeQty
);
event ClosePosition(
address indexed user,
bytes32 indexed positionHash,
bytes32 indexed closeHash,
uint128 closeQty,
CloseInfo closeInfo,
OpenTradeEventV2 ot
);
event PositionDecreased(
address indexed user,
bytes32 indexed positionHash,
bytes32 indexed closeHash,
uint128 closeQty,
CloseInfo closeInfo,
OpenTradeEventV2 ot
);
event ExecuteCloseRejected(
address indexed user,
bytes32 indexed tradeHash,
ExecutionType executionType,
uint128 execPrice,
uint128 marketPrice
);enum ExecutionType { TP, SL, LIQ, ADL }
struct CloseInfo {
uint128 closePrice; // 1e18
int96 fundingFee; // lvToken decimals, signed
uint96 closeFee; // lvToken decimals
int96 pnl; // lvToken decimals, signed
uint96 holdingFee; // lvToken decimals
}ClosePosition and PositionDecreased cover every kind of exit — manual close, take-profit, stop-loss, liquidation and ADL. To distinguish them, look at whether the exit was accompanied by ExecuteDecreaseOrderSuccessful (TP/SL order) or read ExecutionType from the paired execution event.
Legacy close events
CloseTradeSuccessful, CloseTradeSuccessfulV2, ExecuteCloseSuccessful and ExecuteCloseSuccessfulV2 are retained for positions created before the merged-position upgrade. New integrations should index ClosePosition and PositionDecreased.
Limit orders
event OpenLimitOrder(address indexed user, bytes32 indexed orderHash, OpenDataInput data);
event ExecuteLimitOrderSuccessful(address indexed user, bytes32 indexed orderHash);
event ExecuteLimitOrderRejected(address indexed user, bytes32 indexed orderHash, Refund refund);
event LimitOrderRefund(address indexed user, bytes32 indexed orderHash, Refund refund);
event CancelLimitOrder(address indexed user, bytes32 indexed orderHash);
event UpdateOrderTp(address indexed user, bytes32 indexed orderHash, uint256 oldTp, uint256 tp);
event UpdateOrderSl(address indexed user, bytes32 indexed orderHash, uint256 oldSl, uint256 sl);A successful execution emits ExecuteLimitOrderSuccessful and an OpenPosition / PositionIncreased whose sourceHash is the order hash.
Position management
event UpdateTradeTp(address indexed user, bytes32 indexed tradeHash, uint256 oldTp, uint256 tp);
event UpdateTradeSl(address indexed user, bytes32 indexed tradeHash, uint256 oldSl, uint256 sl);
event UpdateMargin(address indexed user, bytes32 indexed tradeHash, uint256 beforeMargin, uint256 margin);TP/SL orders
event DecreaseOrderCreated(
address indexed user,
bytes32 indexed positionHash,
bytes32 indexed orderHash,
DecreaseOrderKind kind, // 0 = TP, 1 = SL
uint128 triggerPrice,
uint128 closeQty,
uint24 broker
);
event DecreaseOrderUpdated(bytes32 indexed orderHash, uint128 triggerPrice, uint128 closeQty, uint24 broker);
event DecreaseOrderCancelled(address indexed user, bytes32 indexed positionHash, bytes32 indexed orderHash);
event ExecuteDecreaseOrderSuccessful(
address indexed user,
bytes32 indexed orderHash,
bytes32 indexed positionHash,
DecreaseOrderKind kind,
uint128 closeQty,
CloseInfo closeInfo,
OpenTradeEventV2 ot
);DecreaseOrderCancelled fires on explicit cancellation, when a full close or liquidation sweeps the position's remaining orders, and when a stale order is cleaned up at execution time.
OpenTradeEventV2
The position snapshot carried by settlement events.
struct OpenTradeEventV2 {
address user;
uint32 userOpenTradeIndex;
uint40 holdingFeeRate; // 1e12
uint128 entryPrice; // 1e18
uint128 qty; // 1e10
address pairBase;
address tokenPay;
address lvToken;
uint96 lvMargin; // lvToken decimals
uint128 stopLoss; // 1e18
uint128 takeProfit; // 1e18
uint24 broker;
bool isLong;
uint32 timestamp;
uint96 lvOpenFee; // lvToken decimals
uint96 lvExecutionFee; // lvToken decimals
int256 longAccFundingFeePerShare; // 1e18
uint256 openBlock;
int128 accruedFundingFee;
uint128 accruedHoldingFee;
}Watching events
import { parseAbiItem } from 'viem'
import { publicClient, DIAMOND } from './config'
const unwatch = publicClient.watchEvent({
address: DIAMOND,
event: parseAbiItem(
'event ClosePosition(address indexed user, bytes32 indexed positionHash, bytes32 indexed closeHash, uint128 closeQty, (uint128,int96,uint96,int96,uint96) closeInfo, (address,uint32,uint40,uint128,uint128,address,address,address,uint96,uint128,uint128,uint24,bool,uint32,uint96,uint96,int256,uint256,int128,uint128) ot)',
),
args: { user: traderAddress },
onLogs: (logs) => {
for (const log of logs) {
const { closeQty, closeInfo } = log.args
console.log('closed', closeQty, 'pnl', closeInfo.pnl)
}
},
})Filtering by user is cheap — it is an indexed topic on nearly every event. positionHash is indexed too, so you can follow one position's whole life with a two-topic filter.
For historical data, the REST API is generally easier than replaying logs: it already joins events into position and trade records.
Refund reasons
PendingTradeRefund, ExecuteLimitOrderRejected and LimitOrderRefund carry a Refund enum. See Error Reference → Refund reasons.