Skip to content

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 ​

EventPhaseMeaning
MarketPendingTraderequestMarket open accepted, collateral locked
OpenPositionsettlementA new position slot was created
PositionIncreasedsettlementAn existing slot grew, entry price blended
PendingTradeRefundsettlementOpen failed; collateral returned
SetOpenTradeEarliestCloseTimesettlementMinimum holding period recorded
ExtraFeeChargedsettlementextraFee paid to the broker's receiver
solidity
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.

solidity
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 ​

EventPhaseMeaning
CloseTradeRequestedrequestClose accepted
ClosePositionsettlementPosition fully closed, slot removed
PositionDecreasedsettlementPartial close settled, remainder open
ExecuteDecreaseOrderSuccessfulsettlementA TP/SL order fired
ExecuteCloseRejectedsettlementClose could not be settled
CloseTradeReceivedsettlementPayout transferred to the trader
solidity
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
);
solidity
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 ​

solidity
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 ​

solidity
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 ​

solidity
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.

solidity
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 ​

ts
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.

Trading perpetuals involves risk. Nothing here is financial advice.