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,      // lvTokenextraFee 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.