Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Proof-labs/trading-sdk/llms.txt
Use this file to discover all available pages before exploring further.
The Proof Exchange emits a structured stream of typed events for every significant state change — order placements, trade executions, position updates, funding settlements, liquidations, and more. You can receive these events through the SDK’s built-in CometBFT WebSocket subscription, or by connecting directly to the gateway’s multiplexed WebSocket feed.
SDK WebSocket Subscription
client.subscribeBlocks(onEvent) connects to the CometBFT WebSocket endpoint, subscribes to both NewBlock and Tx events, and delivers raw event objects to your callback. It returns an unsubscribe function you call to stop receiving events.
import { ExchangeClient, generateKeypair, pubkeyToOwner } from "@proof/trading-sdk";
const { publicKey, privateKey } = generateKeypair();
const address = pubkeyToOwner(publicKey);
const client = new ExchangeClient({ chainId: "exchange-devnet-1" });
client.setPrivateKey(privateKey);
// Subscribe — returns an unsubscribe handle
const unsub = client.subscribeBlocks((event) => {
console.log("Received event:", event);
});
// Later — stop receiving events
unsub();
// Disconnect the WebSocket entirely (includes exponential-backoff reconnect)
client.disconnect();
The WebSocket reconnects automatically with exponential backoff (2 s, 4 s, 8 s … up to 60 s with ±25% jitter) if the connection drops while listeners remain registered. Calling unsub() for the last listener stops reconnection attempts; client.disconnect() closes the socket immediately.
Direct Gateway Feed
For lower latency or custom subscription logic, connect directly to the gateway’s multiplexed WebSocket endpoint:
wss://api.dev.proof.trade/ws
const ws = new WebSocket("wss://api.dev.proof.trade/ws");
ws.onopen = () => console.log("Connected to Proof gateway feed");
ws.onmessage = (msg) => {
const event = JSON.parse(msg.data);
handleEvent(event);
};
ws.onclose = () => console.log("Disconnected");
The gateway multiplexed feed (/ws) delivers the same exchange events as the CometBFT WebSocket but through a single connection managed by the gateway. Use this path when you need minimal overhead or are building a server-side aggregator.
The ExchangeEvent Union Type
Every event has a type discriminant field. The complete ExchangeEvent union covers all engine-emitted events:
type ExchangeEvent =
| OrderPlacedEvent
| OrderCancelledEvent
| TradeExecutedEvent
| FeesCollectedEvent
| DepositedEvent
| WithdrawnEvent
| PositionUpdatedEvent
| PositionClosedEvent
| PriceUpdatedEvent
| MarketCreatedEvent
| AccountLiquidatedEvent
| FundingAppliedEvent
| FundingSettledEvent
| AgentApprovedEvent
| AgentRevokedEvent
| MarketOrderProcessedEvent
| OrderbookLevelUpdatedEvent;
All numeric fields on event objects are stringified numbers — they arrive as string, not number or bigint. Parse them with BigInt(event.price) or Number(event.market) as appropriate.
Event Reference
OrderPlacedEvent
Emitted when a limit order is accepted onto the book.
interface OrderPlacedEvent {
type: "OrderPlaced";
orderId: string; // engine-assigned order ID
market: string; // market identifier
owner: string; // hex-encoded owner address
side: string; // "Buy" or "Sell"
price: string; // limit price in cents
quantity: string; // quantity in contracts
clientOrderId: string; // client ID, or "0" if absent
}
OrderCancelledEvent
Emitted when a resting order is cancelled — by user request, IOC expiry, or liquidation.
interface OrderCancelledEvent {
type: "OrderCancelled";
orderId: string;
market: string;
owner: string;
reason: string; // e.g. "user_requested", "liquidation"
clientOrderId: string; // or "0"
originalQuantity: string; // quantity accepted onto the book
remainingQuantity: string; // quantity resting when cancelled
filledQuantity: string; // cumulative maker fills before cancellation
}
TradeExecutedEvent
Emitted once per fill between a maker and a taker.
interface TradeExecutedEvent {
type: "TradeExecuted";
fillId: string;
market: string;
price: string; // execution price in cents
quantity: string; // fill quantity in contracts
makerOrderId: string;
makerClientOrderId: string; // or "0"
makerOwner: string;
makerSide: string; // "Buy" or "Sell"
takerOwner: string;
takerClientOrderId: string; // or "0"
takerFee: string; // microUSDC (non-negative)
makerFee: string; // microUSDC (non-negative)
}
FeesCollectedEvent
Emitted alongside every TradeExecuted to summarize the net fee transfer.
interface FeesCollectedEvent {
type: "FeesCollected";
market: string;
takerOwner: string;
takerFee: string; // signed microUSDC (negative = rebate)
makerOwner: string;
makerFee: string; // signed microUSDC (negative = rebate)
}
PositionUpdatedEvent
Emitted whenever a position’s size or entry price changes due to a fill.
interface PositionUpdatedEvent {
type: "PositionUpdated";
owner: string;
market: string;
side: string; // "Buy" = long, "Sell" = short
entryPrice: string; // weighted-average entry price in cents
size: string; // absolute size in contracts
}
PositionClosedEvent
Emitted when a position reaches zero size.
interface PositionClosedEvent {
type: "PositionClosed";
owner: string;
market: string;
realizedPnl: string; // signed microUSDC (positive = profit)
}
MarketOrderProcessedEvent
Emitted once per market-order transaction as the authoritative fill summary.
interface MarketOrderProcessedEvent {
type: "MarketOrderProcessed";
market: string;
owner: string;
side: string;
requestedQuantity: string; // quantity originally requested
filledQuantity: string; // quantity actually filled
clientOrderId?: string; // if a client ID was provided
}
| Outcome | Condition |
|---|
| Full fill | filledQuantity === requestedQuantity |
| Partial fill | 0 < filledQuantity < requestedQuantity |
| No fill | filledQuantity === "0" |
MarketOrderProcessed is the definitive “did my market order fill?” signal. Without it, you’d have to count TradeExecuted events, which may arrive out of order in multi-stream setups.
FundingAppliedEvent
Emitted at each funding interval for a market.
interface FundingAppliedEvent {
type: "FundingApplied";
market: string;
fundingRateBps: string; // signed bps (positive = longs pay shorts)
cumulativeFunding: string; // cumulative funding index after this tick
timestampMs: string; // block timestamp in milliseconds
}
FundingSettledEvent
Emitted once per open position at each funding tick.
interface FundingSettledEvent {
type: "FundingSettled";
owner: string;
market: string;
payment: string; // signed microUSDC (positive = received, negative = paid)
}
AccountLiquidatedEvent
Emitted when a position is liquidated due to insufficient maintenance margin.
interface AccountLiquidatedEvent {
type: "AccountLiquidated";
owner: string;
market: string;
side: string;
size: string; // size liquidated in contracts
markPrice: string; // mark price at liquidation in cents
realizedPnl: string; // signed realized PnL in microUSDC
}
OrderbookLevelUpdatedEvent
Emitted after any mutation to a price level — absolute post-mutation state.
interface OrderbookLevelUpdatedEvent {
type: "OrderbookLevelUpdated";
market: string;
side: string; // "Buy" or "Sell"
price: string; // price level in micro-USDC
totalQuantity: string; // total resting quantity at this level
orderCount: string; // resting order count at this level
}
PriceUpdatedEvent
Emitted when the oracle price is updated for a market.
interface PriceUpdatedEvent {
type: "PriceUpdated";
market: string;
price: string; // new oracle price in cents (2 dp)
}
AgentApprovedEvent / AgentRevokedEvent
Emitted when a delegate agent wallet is approved or revoked.
interface AgentApprovedEvent {
type: "AgentApproved";
owner: string; // hex-encoded owner
agent: string; // hex-encoded agent address
agentPubkey: string; // hex-encoded Ed25519 public key (32 bytes)
}
interface AgentRevokedEvent {
type: "AgentRevoked";
owner: string;
agent: string;
agentPubkey: string;
}
Complete Event Handler Pattern
A robust handler switches on event.type and handles each variant explicitly. Filter for the events you care about and ignore the rest.
import type { ExchangeEvent } from "@proof/trading-sdk";
const myAddressHex = client.getAddressHex()!;
const unsub = client.subscribeBlocks((raw) => {
// The SDK delivers raw CometBFT event data — cast to ExchangeEvent
const event = raw as unknown as ExchangeEvent;
switch (event.type) {
case "OrderPlaced": {
if (event.owner === myAddressHex) {
console.log(`Order placed: id=${event.orderId} price=${event.price} qty=${event.quantity}`);
}
break;
}
case "OrderCancelled": {
if (event.owner === myAddressHex) {
console.log(`Order cancelled: id=${event.orderId} reason=${event.reason}`);
}
break;
}
case "TradeExecuted": {
const involvedMe = event.makerOwner === myAddressHex || event.takerOwner === myAddressHex;
if (involvedMe) {
const isMaker = event.makerOwner === myAddressHex;
const fee = isMaker ? event.makerFee : event.takerFee;
console.log(
`Trade: market=${event.market} price=${event.price} qty=${event.quantity} ` +
`role=${isMaker ? "maker" : "taker"} fee=${fee}µUSDC`
);
}
break;
}
case "MarketOrderProcessed": {
if (event.owner === myAddressHex) {
const filled = BigInt(event.filledQuantity);
const requested = BigInt(event.requestedQuantity);
if (filled === requested) {
console.log(`Market order fully filled: ${filled} contracts on market ${event.market}`);
} else if (filled === 0n) {
console.warn(`Market order not filled — no counterparty on market ${event.market}`);
} else {
console.warn(`Partial fill: ${filled}/${requested} contracts on market ${event.market}`);
}
}
break;
}
case "PositionUpdated": {
if (event.owner === myAddressHex) {
console.log(
`Position: market=${event.market} side=${event.side} ` +
`size=${event.size} entry=${event.entryPrice}`
);
}
break;
}
case "PositionClosed": {
if (event.owner === myAddressHex) {
const pnl = BigInt(event.realizedPnl);
const pnlUsd = (Number(pnl) / 1_000_000).toFixed(2);
const sign = pnl >= 0n ? "+" : "";
console.log(`Position closed: market=${event.market} PnL=${sign}$${pnlUsd}`);
}
break;
}
case "FundingSettled": {
if (event.owner === myAddressHex) {
const payment = BigInt(event.payment);
const paymentUsd = (Number(payment) / 1_000_000).toFixed(4);
const sign = payment >= 0n ? "+" : "";
console.log(`Funding: market=${event.market} payment=${sign}$${paymentUsd}`);
}
break;
}
case "AccountLiquidated": {
if (event.owner === myAddressHex) {
console.error(
`LIQUIDATED: market=${event.market} side=${event.side} size=${event.size} ` +
`markPrice=${event.markPrice} pnl=${event.realizedPnl}`
);
}
break;
}
case "FundingApplied": {
// Market-wide funding tick — log for all markets
console.log(
`Funding applied: market=${event.market} rate=${event.fundingRateBps}bps ` +
`cumulative=${event.cumulativeFunding}`
);
break;
}
case "OrderbookLevelUpdated": {
// High-frequency — only log significant changes
if (Number(event.orderCount) === 0) {
console.log(`Level cleared: market=${event.market} side=${event.side} price=${event.price}`);
}
break;
}
// Other event types: PriceUpdated, FeesCollected, Deposited, Withdrawn,
// AgentApproved, AgentRevoked, MarketCreated — handle as needed
default:
break;
}
});
// Run for 60 seconds then clean up
setTimeout(() => {
unsub();
client.disconnect();
console.log("Disconnected");
}, 60_000);
Cleanup
Always call client.disconnect() when you are done to cleanly close the WebSocket and cancel any pending reconnection timers.
// Remove one listener but keep the socket open
const unsub = client.subscribeBlocks(handler);
unsub();
// Close the socket and cancel reconnects entirely
client.disconnect();
In long-running server processes, register a SIGTERM / SIGINT handler that calls client.disconnect() to avoid dangling WebSocket connections and event listeners.