Skip to content

Collecting Orders

On VM chains, assets can be pulled from users via approvals – a feature not available on non-VM chains – instead the sequence of operation has been modified to allow the user to push assets to the solver. The easiest routes originate from a VM chain – these routes are generally permissionless – while routes originating from BTC are more difficult – these routes are generally permissioned.

Pulling Orders

The simplest way to collect orderflow from EVM is via polling. The order server stores orders in a dictionary format, allowing for straightforward parsing by integrators and provides a high level of transparency in the implementation.

// The type parameter (DutchAuctionData.type) is not be submitted on-chain but is used to differentiate order types.
type DutchAuctionData = {
type: "DutchAuction"; // Not to be submitted
verificationContext: string;
verificationContract: string;
proofDeadline: number;
challengeDeadline: number;
collateralToken: string;
fillerCollateralAmount: string;
challengerCollateralAmount: string;
localOracle: string;
slopeStartingTime: number;
inputSlopes: string[];
outputSlopes: string[];
inputs: {}[];
outputs: {}[];
};
type LimitOrderData = {
type: "LimitOrder"; // Not to be submitted
proofDeadline: number;
challengeDeadline: number;
collateralToken: string;
fillerCollateralAmount: string;
challengerCollateralAmount: string;
localOracle: string;
inputs: {}[];
outputs: {}[];
};
// With the CrossChainOrder defined as such:
type CrossChainOrder = {
settlementContract: string;
swapper: string;
nonce: string;
originChainId: number;
initiateDeadline: number;
fillDeadline: number;
orderData: DutchAuctionData | LimitOrderData;
};
type OrderDto = {
order: CrossChainOrder;
quote: {
fromAsset: string;
toAsset: string;
toPrice: string;
fromPrice: string;
intermediary: "USD" | "EUR" | "BTC" | string; // explicit string types here are examples.
discount: string;
};
signature: string;
submitTime: number;
};
type PaginationMeta = {
total: number;
limit: number;
offset: number;
};
type GetOrdersResponse = {
data: OrderDto[];
pagination: PaginationMeta;
};
async function getOrders(): GetOrdersResponse {
const API_URI = "https://crosscats-api-staging.catalyst.exchange";
const API_KEY = "your_api_key_here";
const orderServerResponse = await fetch<GetOrdersResponse>(
API_URI + "/orders",
{
headers: {
"x-api-key": API_KEY,
Accept: "application/json",
},
}
);
if (!orderServerResponse.ok) {
throw new Error(`HTTP error! status: ${orderServerResponse.status}`);
}
const fetchedOrders = await orderServerResponse.json();
return fetchedOrders;
}

Subscribing to Orders

Subscribing to orders allow you to listen directly to the order flow. This section outlines how to subscribe to new orders using a WebSocket connection, allowing for real-time updates without the need to continuously poll the order server. By leveraging WebSocket, the Catalyst order server broadcasts new orders as they arrive, offering a significant reduction in latency but at the cost of increased complexity due to the need for a persistent connection and local filtering of incoming data.

Instead of polling for new orders, you can establish a WebSocket connection to the Catalyst order server. The server provides a WebSocket endpoint that pushes new order data to subscribers in real time. However, unlike polling, the data received through WebSocket is not pre-filtered. This means every order event will be pushed to your application, and it’s up to your implementation to manage and filter these events locally.

Below is a simplified implementation in pure JavaScript that demonstrates how to connect to the WebSocket server, handle incoming messages, respond to ping events, and automatically attempt to reconnect if the connection is lost.

(TODO update typescript block)

const WebSocket = require("ws");
// Configuration variables
const wsUri = process.env.ORDER_SERVER_WS_URI; // Set your WebSocket server URI
const apiKey = process.env.ORDER_SERVER_API_KEY; // Set your API key
const reconnectInterval = 5000; // Reconnect interval in milliseconds
let ws;
// Function to connect to the WebSocket server
function connectToOrderServer() {
ws = new WebSocket(wsUri, {
headers: {
"x-api-key": apiKey,
},
});
ws.on("open", () => {
console.log("Connected to WebSocket server");
});
ws.on("message", (data) => {
try {
const parsedData = JSON.parse(data.toString());
console.log("Received message:", parsedData);
switch (parsedData.event) {
case "ping":
handleReceivePing();
break;
case "quote-request":
handleReceiveQuoteRequest(parsedData, ws);
break;
case "order":
handleReceiveOrder(parsedData, ws);
break;
default:
console.log("Unknown message type:", parsedData);
}
} catch (error) {
console.error("Error parsing JSON:", error);
}
});
ws.on("error", (error) => {
console.error("WebSocket error:", error);
});
ws.on("close", () => {
console.log("Disconnected from WebSocket");
reconnect();
});
}
// Function to handle ping messages, you will be automatically disconnected if you don't respond to ping messages
function handleReceivePing() {
ws.send(JSON.stringify({ event: "pong" }));
}
// Function to handle quote requests
function handleReceiveQuoteRequest(data, ws) {
console.log("Handling quote request:", data);
// Add your custom handling logic here
}
// Function to handle orders
function handleReceiveOrder(data, ws) {
console.log("Handling order:", data);
// Add your custom handling logic here
}
// Function to attempt reconnection
function reconnect() {
console.log("Attempting to reconnect...");
setTimeout(() => {
ws.close(); // Close any existing connection
connectToOrderServer(); // Attempt to reconnect
}, reconnectInterval);
}
// Start listening to the order server
connectToOrderServer();

Evaluating Orders

After fetching an order, the solver must thoroughly evaluate it to determine its viability and potential execution. To facilitate this evaluation, several contextual pointers are available within the returned order data. Key aspects to consider include:

  1. Quote Validation: Use the OrderDto.quote field to access the price context, which provides the pricing details for the inputs and outputs of the order. If you trust the order server, you can primarily rely on this quote to validate the order’s pricing. However, it’s crucial to verify that the solver supports the specific origin chain (OrderDto.order.originChainId) and output chains (OrderDto.order.orderData.outputs[...].chainId) as well as their respective tokens (input[].token and output[].token). These parameters are guaranteed to be present across all order types.

  2. Solver-Exclusive Orders: Some orders may initially be restricted to specific solvers. This is indicated by the OrderDto.order.orderData.verificationContract field. If this field is defined and not equal to address(0), the order is exclusive to the designated solver until the slopeStartingTime elapses, after which the order becomes available for anyone to fulfill.

  3. Mutually Exclusive Orders: Be aware of potential conflicts between orders. If you encounter two orders with the same OrderDto.order.swapper and OrderDto.order.nonce, these orders are mutually exclusive, meaning only one of them can be submitted on-chain. This mechanism prevents double submissions and ensures the integrity of the order processing.

Evaluating orders carefully ensures that solvers can accurately determine the feasibility of executing an order, adhere to exclusivity rules, and avoid conflicts, thereby maintaining the integrity and efficiency of the order fulfillment process.