Skip to main content

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.

ExchangeClient is the single entry point for all interactions with the Proof Exchange. It handles Ed25519 key management, timestamp-nonce allocation, chain ID resolution, signed transaction encoding, and every read endpoint exposed by the Go API server and CometBFT RPC layer. Instantiate once, load a private key, and call the appropriate method group.

Constructor

new ExchangeClient(opts?: ExchangeClientOptions)
Creates a new client instance. All options are optional; the defaults target the public Proof devnet. See Client Options for a full reference.
import { ExchangeClient } from "@proof/trading-sdk";

// Devnet — all defaults
const client = new ExchangeClient({ chainId: "exchange-devnet-1" });

// Local development stack
const client = new ExchangeClient({
  rpcUrl: "http://localhost:26657",
  apiUrl: "http://localhost:8080",
  gatewayUrl: "http://localhost:9080",
  chainId: "proof-dev",
});
opts
ExchangeClientOptions
Configuration object. When omitted entirely the client targets the Proof devnet at https://api.dev.proof.trade. See Client Options for every field.

Key Management

Before any transaction can be submitted you must load a private key. The key is stored in memory for the lifetime of the client instance.

setPrivateKey

setPrivateKey(key: Uint8Array): void
Loads a 32-byte Ed25519 private key. Simultaneously derives and caches the public key, 20-byte owner address, and hex-encoded address string.
import { generateKeypair, pubkeyToOwner } from "@proof/trading-sdk";

const { privateKey } = generateKeypair();
client.setPrivateKey(privateKey);
key
Uint8Array
required
A 32-byte Ed25519 private key scalar.

getAddress

getAddress(): Uint8Array | null
Returns the cached 20-byte owner address derived from the loaded public key, or null if no key has been set.
return
Uint8Array | null
The 20-byte address as a Uint8Array, or null before setPrivateKey is called.

getAddressHex

getAddressHex(): string | null
Returns the hex-encoded owner address (without a 0x prefix), or null if no key has been set. This string is accepted by every query endpoint that takes an addressHex parameter.
return
string | null
Lowercase hex address string (40 characters), or null before setPrivateKey is called.

Chain

ready

ready(): Promise<void>
Pre-warms the chain ID resolution cache. The client resolves its chain ID lazily on the first submitTx call; calling ready() at startup surfaces /status-fetch errors during initialization rather than mid-flow. This method is optional — submitTx resolves the chain ID automatically — but recommended for production code where you want to fail fast.
const client = new ExchangeClient();
await client.ready(); // throws here if /status is unreachable
client.setPrivateKey(privateKey);

status

status(): Promise<{ latestHeight: number; latestAppHash: string }>
Fetches the CometBFT /status endpoint and returns the latest committed block height and app hash.
const { latestHeight, latestAppHash } = await client.status();
console.log(`Chain head: block ${latestHeight}`);
latestHeight
number
The block height of the most recently committed block.
latestAppHash
string
Hex-encoded application state hash at latestHeight.

Transaction Submission

The client exposes two submission modes. Both submit the same V3 signed MessagePack envelope; they differ only in how long they wait and what they return.

TxResult

Every submission method returns a TxResult:
interface TxResult {
  code: number;      // 0 = success; non-zero = ExecError code
  hash: string;      // Hex-encoded transaction hash (uppercase)
  height?: number;   // Block height of inclusion (after commit only)
  log?: string;      // Human-readable error message on failure
  events?: TxEvent[];// ABCI events emitted by the transaction
}
code
number
0 on success. Non-zero values correspond to ExecError codes from the engine. Common codes:
CodeMeaning
12Insufficient margin
17Invalid signature
21Invalid or duplicate timestamp nonce — retry
34Post-only order would cross
401Missing or invalid X-Api-Key
429Rate limited by gateway
hash
string
Uppercase hex-encoded SHA-256 hash of the raw transaction bytes. Empty string on gateway-level transport failures.
height
number
Block height at which the transaction was included. Only populated by submitTxCommit and the background DeliverTx verifier after inclusion is confirmed.
log
string
Human-readable error description. Empty on success.
events
TxEvent[]
ABCI events emitted by the transaction. Only populated after block inclusion (not available from CheckTx).

submitTx

submitTx(action: Action): Promise<TxResult>
Signs and broadcasts a transaction, returning as soon as CometBFT’s CheckTx (or the gateway) responds. On code === 0 a background verifier is spawned that polls /tx?hash=... for up to 5 seconds to capture the DeliverTx result. Collect those results with awaitPendingVerifies. This is the preferred high-throughput path. The timestamp nonce is allocated automatically.
import { Side } from "@proof/trading-sdk";

const result = await client.submitTx({
  type: "PlaceOrder",
  data: {
    market: 1,
    owner: client.getAddress()!,
    side: Side.Buy,
    price: 50_000_000n, // $500,000.00 in cents
    quantity: 1n,
    postOnly: true,
  },
});

if (result.code !== 0) {
  console.error(`Order rejected: [${result.code}] ${result.log}`);
}
action
Action
required
A discriminated-union action object. See Actions below.
return
Promise<TxResult>
Resolves after CheckTx with code === 0 for acceptance or a non-zero code for rejection. height is not set — use submitTxCommit or awaitPendingVerifies if you need confirmed inclusion.

submitTxCommit

submitTxCommit(action: Action): Promise<TxResult>
Submits a transaction and synchronously polls /tx?hash=... until block inclusion is confirmed or the 9-second deadline is reached. Returns the DeliverTx result including height and events. Use this when downstream logic depends on the transaction’s on-chain effect (e.g. open a position, then immediately query account).
const result = await client.submitTxCommit({
  type: "PlaceOrder",
  data: {
    market: 1,
    owner: client.getAddress()!,
    side: Side.Sell,
    price: 65_000_000n,
    quantity: 2n,
  },
});

console.log(`Included at block ${result.height}`);
action
Action
required
A discriminated-union action object.
return
Promise<TxResult>
Resolves with a fully-populated TxResult including height and events. Returns code: -1 with a timeout log if inclusion is not seen within 9 seconds.

awaitPendingVerifies

awaitPendingVerifies(): Promise<TxResult[]>
Waits for all in-flight background DeliverTx verifiers (spawned by prior submitTx calls) to settle, then returns the collected results. The internal buffer is drained on each call — subsequent calls return only results that completed after the previous drain. Call this before issuing a transaction that depends on a previous transaction’s state having landed.
// Submit a batch
for (const order of orders) {
  await client.submitTx({ type: "PlaceOrder", data: order });
}

// Wait for all to land
const deliveryResults = await client.awaitPendingVerifies();
const failed = deliveryResults.filter((r) => r.code !== 0);
if (failed.length > 0) {
  console.warn(`${failed.length} orders failed at inclusion`, failed);
}
return
Promise<TxResult[]>
Array of DeliverTx results. Entries with code !== 0 passed CheckTx but failed at block inclusion and should be logged or retried.

Action Type

Action is a discriminated union. Pass the appropriate type string and data object:
type Action =
  | { type: "PlaceOrder";         data: PlaceOrder }
  | { type: "CancelOrder";        data: CancelOrder }
  | { type: "CancelClientOrder";  data: CancelClientOrder }
  | { type: "CancelAllOrders";    data: CancelAllOrders }
  | { type: "CancelReplaceOrder"; data: CancelReplaceOrder }
  | { type: "AmendOrder";         data: AmendOrder }
  | { type: "AtomicBasketOrder";  data: AtomicBasketOrder }
  | { type: "MarketOrder";        data: MarketOrder }
  | { type: "WithdrawRequest";    data: WithdrawRequest }
  | { type: "ApproveAgent";       data: ApproveAgent }
  | { type: "RevokeAgent";        data: RevokeAgent }
  | { type: "ClosePosition";      data: ClosePosition }
  // … and more admin/relayer actions — see src/types.ts
Common action examples:
// Limit order
{ type: "PlaceOrder", data: { market: 1, owner: addr, side: Side.Buy,
    price: 50_000_000n, quantity: 1n } }

// Market order (crosses immediately)
{ type: "MarketOrder", data: { market: 1, owner: addr, side: Side.Sell,
    quantity: 1n } }

// Cancel by engine order ID
{ type: "CancelOrder", data: { orderId: 42n, owner: addr } }

// Cancel all orders on a market
{ type: "CancelAllOrders", data: { owner: addr, market: 1 } }

// Close entire position at oracle price
{ type: "ClosePosition", data: { market: 1, owner: addr } }

Reads

All read methods call the Go API server at apiUrl. The addressHex parameter is optional on most methods — when omitted the client uses the address derived from the loaded private key.

queryOrderbook

queryOrderbook(market: number): Promise<Orderbook>
Fetches the full order book snapshot for a market, decoded from MessagePack.
const book = await client.queryOrderbook(1);
console.log(`Best bid: ${book.bids[0]?.price}`);
console.log(`Best ask: ${book.asks[0]?.price}`);
market
number
required
Integer market identifier (e.g. 1 for BTC-PERP).
return
Promise<Orderbook>
Each OrderbookLevel:

queryOpenOrders

queryOpenOrders(addressHex?: string): Promise<OpenOrder[]>
Returns all resting orders for an account. Returns an empty array if the account has no open orders or if no address is available.
const orders = await client.queryOpenOrders();
for (const o of orders) {
  console.log(`Order ${o.id}: ${o.side} ${o.quantity} @ ${o.price} on market ${o.market}`);
}
addressHex
string
Hex-encoded owner address (40 characters). Defaults to the address derived from the loaded private key.
return
Promise<OpenOrder[]>

queryMarkets

queryMarkets(): Promise<MarketConfig[]>
Returns all registered market configurations. Used to discover market IDs, fee rates, margin requirements, and tick/lot sizes.
const markets = await client.queryMarkets();
markets.forEach((m) => console.log(`Market ${m.market}: IM=${m.imBps}bps`));
return
Promise<MarketConfig[]>

queryAccount

queryAccount(addressHex?: string): Promise<AccountInfo | null>
Fetches full account state: balance, open positions with margin enrichments, equity, and margin requirements. Returns null if no address is available.
const acct = await client.queryAccount();
if (acct) {
  const usdcBalance = Number(acct.balance) / 1e6;
  console.log(`Balance: $${usdcBalance.toFixed(2)}`);
  console.log(`Equity:  $${(Number(acct.equity) / 1e6).toFixed(2)}`);
  console.log(`Positions: ${acct.positions.length}`);
}
addressHex
string
Hex-encoded owner address. Defaults to the address derived from the loaded private key.
return
Promise<AccountInfo | null>

queryTicker

queryTicker(market: number): Promise<Ticker | null>
Returns a one-round-trip market summary bundling 24-hour statistics, funding data, and top-of-book in a single response. Returns null for unknown market IDs.
const ticker = await client.queryTicker(1);
if (ticker) {
  console.log(`Last: ${ticker.lastPrice}`);
  console.log(`24h vol (contracts): ${ticker.volume24hContracts}`);
  console.log(`24h change (bps): ${ticker.change24hBps}`);
}
market
number
required
Integer market identifier.
return
Promise<Ticker | null>

queryHealth

queryHealth(): Promise<{ status: string; height: number }>
Checks the health of the API server. Useful as a liveness probe.
const health = await client.queryHealth();
console.log(health); // { status: "ok", height: 12345 }
status
string
Server health status string (e.g. "ok").
height
number
Latest block height seen by the API server.

queryAdlQueue

queryAdlQueue(market: number): Promise<AdlQueueEntry[]>
Returns the auto-deleveraging queue for a market — profitable positions sorted by adlScore descending (highest-risk first). Use the position’s rank index divided by the total entry count to compute a per-position ADL percentile.
const queue = await client.queryAdlQueue(1);
const myEntry = queue.find((e) =>
  e.owner.toString() === client.getAddress()?.toString()
);
if (myEntry) {
  const percentile = (queue.indexOf(myEntry) / queue.length) * 100;
  console.log(`ADL percentile: ${percentile.toFixed(0)}%`);
}
market
number
required
Integer market identifier.
return
Promise<AdlQueueEntry[]>

queryWithdrawal

queryWithdrawal(id: bigint): Promise<WithdrawalRecord | null>
Looks up a withdrawal record by its engine-assigned ID. Returns null for unknown IDs (the engine encodes “not found” as MessagePack nil, not HTTP 404).
const record = await client.queryWithdrawal(7n);
if (record) {
  console.log(`Status: ${record.status}`); // "Pending" | "Completed" | "Failed"
  console.log(`Amount: ${record.amount} µUSDC`);
}
id
bigint
required
Engine-assigned withdrawal ID, obtained from a prior WithdrawRequest result or history query.
return
Promise<WithdrawalRecord | null>

History

History endpoints return chronological event logs for an account, newest first. All time filter parameters are Unix millisecond timestamps.

queryHistoryDeposits

queryHistoryDeposits(
  addressHex?: string,
  opts?: { fromMs?: number; toMs?: number; limit?: number }
): Promise<HistoryCashFlow[]>
Returns every deposit_confirmed event for an address, newest first.
const deposits = await client.queryHistoryDeposits(undefined, {
  fromMs: Date.now() - 7 * 24 * 60 * 60 * 1000, // last 7 days
  limit: 50,
});
addressHex
string
Hex-encoded owner address. Defaults to the loaded key’s address.
opts.fromMs
number
Start of the time window as a Unix millisecond timestamp. Unbounded when omitted.
opts.toMs
number
End of the time window as a Unix millisecond timestamp. Unbounded when omitted.
opts.limit
number
Maximum number of rows to return. Server-side cap is 1,000.
return
Promise<HistoryCashFlow[]>

queryHistoryWithdrawals

queryHistoryWithdrawals(
  addressHex?: string,
  opts?: { fromMs?: number; toMs?: number; limit?: number }
): Promise<HistoryCashFlow[]>
Returns the withdrawal lifecycle log for an address — covering withdraw_requested, withdrawal_confirmed, and withdrawal_failed events, newest first. Filter by kind client-side to show only pending requests.
addressHex
string
Hex-encoded owner address. Defaults to the loaded key’s address.
opts.fromMs
number
Start of the time window (Unix ms). Unbounded when omitted.
opts.toMs
number
End of the time window (Unix ms). Unbounded when omitted.
opts.limit
number
Maximum rows to return. Server-side cap is 1,000.
return
Promise<HistoryCashFlow[]>

HistoryCashFlow fields


queryHistoryResolutions

queryHistoryResolutions(
  addressHex?: string,
  opts?: {
    impactMarketId?: number;
    fromMs?: number;
    toMs?: number;
    limit?: number;
  }
): Promise<HistoryResolution[]>
Returns per-user position-at-resolution rows — one entry per settlement or voided-conditional snapshot. Feeds the Portfolio “Resolved” tab.
addressHex
string
Hex-encoded owner address. Defaults to the loaded key’s address.
opts.impactMarketId
number
Filter to a specific impact-market family ID.
opts.fromMs
number
Start of the time window (Unix ms).
opts.toMs
number
End of the time window (Unix ms).
opts.limit
number
Maximum rows to return. Server-side cap is 1,000.
return
Promise<HistoryResolution[]>

queryHistoryPositions

queryHistoryPositions(
  addressHex?: string,
  opts?: {
    market?: number;
    fromMs?: number;
    toMs?: number;
    limit?: number;
  }
): Promise<HistoryPositionSnapshot[]>
Returns point-in-time position snapshots written after each fill that changes a position. A row with size === "0" is a close event. Results are newest first.
const snapshots = await client.queryHistoryPositions(undefined, {
  market: 1,
  limit: 100,
});
const closes = snapshots.filter((s) => s.size === "0");
console.log(`${closes.length} closed positions on market 1`);
addressHex
string
Hex-encoded owner address. Defaults to the loaded key’s address.
opts.market
number
Filter to a specific market ID.
opts.fromMs
number
Start of the time window (Unix ms).
opts.toMs
number
End of the time window (Unix ms).
opts.limit
number
Maximum rows to return. Server-side cap is 5,000.
return
Promise<HistoryPositionSnapshot[]>

Blocks

Direct CometBFT RPC block accessors. These fetch raw JSON-RPC results and return them unmodified.

getBlock

getBlock(height?: number): Promise<Record<string, unknown>>
Fetches a block from the CometBFT RPC. When height is omitted, returns the latest committed block.
const latest = await client.getBlock();
const specific = await client.getBlock(12345);
height
number
Block height to fetch. Omit for the latest block.
return
Promise<Record<string, unknown>>
Raw CometBFT block result object. Contains block.header, block.data.txs, and related fields.

getBlockResults

getBlockResults(height: number): Promise<Record<string, unknown>>
Fetches ABCI results for a specific block — the per-transaction DeliverTx outcomes including events.
const results = await client.getBlockResults(12345);
height
number
required
Block height to fetch results for. Unlike getBlock, a height is required.
return
Promise<Record<string, unknown>>
Raw CometBFT block_results object. Contains txs_results, begin_block_events, end_block_events, and consensus_param_updates.

Events

subscribeBlocks

subscribeBlocks(
  onEvent: (event: Record<string, unknown>) => void
): () => void
Opens a WebSocket connection to the CometBFT node (at wsUrl) and delivers NewBlock and Tx events to the callback. The connection reconnects automatically with exponential backoff (2s → 4s → … capped at 60s with ±25% jitter) if it closes unexpectedly. Multiple calls register multiple listeners on the same underlying socket. Returns an unsubscribe function that removes the specific listener; the socket is kept open while any listener remains.
const unsubscribe = client.subscribeBlocks((event) => {
  const type = (event as { type?: string }).type;

  if (type === "TradeExecuted") {
    console.log("Trade:", event);
  }

  if (type === "NewBlock") {
    const block = event as { value?: { block?: { header?: { height?: string } } } };
    console.log("New block:", block.value?.block?.header?.height);
  }
});

// Later, remove this listener:
unsubscribe();
onEvent
(event: Record<string, unknown>) => void
required
Callback invoked for every NewBlock and Tx event received over the WebSocket. The event object is the result.data field of the CometBFT subscription message.
return
() => void
An unsubscribe function. Call it to remove this listener. If no listeners remain after unsubscribing, the WebSocket reconnect timer is left active until disconnect() is called.

disconnect

disconnect(): void
Closes the WebSocket connection and cancels any pending reconnect timer. Call this when the client instance is no longer needed to avoid background reconnection loops.
// Clean up on shutdown
client.disconnect();

Build docs developers (and LLMs) love