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; }
import requests
def get_orders(): response = requests.get(API_URL + "orders/") fetched_orders = response.json() return fetched_orders
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:
-
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
andoutput[].token
). These parameters are guaranteed to be present across all order types. -
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 toaddress(0)
, the order is exclusive to the designated solver until theslopeStartingTime
elapses, after which the order becomes available for anyone to fulfill. -
Mutually Exclusive Orders: Be aware of potential conflicts between orders. If you encounter two orders with the same
OrderDto.order.swapper
andOrderDto.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.