This is an in-depth guide to onboarding market makers, focusing on how orders are processed and signed.
We will explain how to use the provided code, break down key concepts such as signature generation, and guide you through the modules used (e.g., nanoid, ethers).
Our platform interacts with the order book API to facilitate trading using buy and sell orders based on price changes.
Dependencies
nanoid: Generates unique, secure IDs used for each order.
ethers.js (version 5.7.2): A library that provides utilities for interacting with Ethereum, particularly useful for signing messages (orders in our case).
Key Concepts and Workflow
1. Price Change Orders
Market makers often need to update their orders based on price changes. The processOrders function automates this by creating and sending orders to the platform with unique identifiers and signatures.
import { nanoid } from "nanoid";
import { handleOrderSignature } from "./utils";
export NEXT_API_URL=https://orderbook.filament.finance/sei;
nanoid: This package is used to create a unique identifier for each order. Each generated ID is lowercase to maintain consistency.
handleOrderSignature: This utility function is used to sign the order ID using the provided private key.
2. Signature Generation: handleOrderSignature
The function handleOrderSignature is critical for ensuring the authenticity of each order by creating a cryptographic signature.
export const handleOrderSignature = async (orderId: string, signature: string) => {
const signer = new ethers.Wallet(signature); // Initialize a new ethers.js Wallet with the private key.
const orderSignature = await signer.signMessage(orderId); // Sign the order ID to generate a signature.
return orderSignature; // Return the signature for order validation.
};
Explanation:
orderId: The unique order ID generated by nanoid.
signature: The private key used to sign the order (passed as signingKey in processOrders).
The function uses ethers.js to sign the message, ensuring that only the owner of the private key can create valid orders.
Purpose of Signature:
Signatures verify the identity of the market maker submitting the order. When an order is signed with the private key, the platform can trust the authenticity of the request, making it difficult for malicious actors to forge orders.
3. Function: processOrders
The processOrders function handles the creation of orders and prepares them to be sent to the API.
export async function processOrders(
price: any,
isBuy: boolean,
asset: string,
account: string,
signingKey: string
) {
const id = nanoid().toLowerCase(); // Generating a unique order ID.
const signature = await handleOrderSignature(id, signingKey);
const payload = {
type: 'order',
referralCode: null,
orders: [
{
account: account.toLowerCase(), // Market maker's account address.
indexToken: asset, // The asset to be traded.
orderId: id, // The unique order ID.
signature: signature, // Signature of the order for verification.
isBuy, // Boolean indicating buy or sell.
size: 15000, // Order size (e.g., 15,000 units).
leverage: 30, // Leverage applied (e.g., 30x).
reduceOnly: false, // Reduce-only flag (false in this example).
orderType: {
type: 'limit',
limit: {
tif: 'Gtc', // Time-in-force for the order ("Good 'til canceled").
limitPrice: price, // The price associated with the order.
},
},
},
],
};
}
Explanation of Parameters:
price: Price to place orders
isBuy: A boolean value indicating whether the order is a buy (true) or a sell (false).
asset: The asset or token being traded (e.g., SOL, BTC).
account: The wallet address of the market maker, which will be used for the order.
signingKey: The private key used to sign the order for verification purposes.
Workflow:
For each price change, it generates a unique order ID using nanoid().
The order ID is signed using the handleOrderSignature function.
An order payload is created with information like the asset being traded, order size, leverage, and limit price.
The payload can be sent to the API for order execution.
4. Sending the Order
Once the payload is created, it can be sent to the platform’s API at the endpoint provided in the configuration. The API will process the order and execute it according to the details provided (e.g., size, limit price, etc.).
This endpoint give insight on available deposited balance in USDC. Basically amount of collateral free to use.
Input:
account: User address
Output:
USDC value
Eg: 343.917502459535209000
URL: {{baseUrl}}/v1/positions/balance/:account
It gives insight on USDC locked with the position, adding or subtracting unrealized PnL and subtracting fees.
Input:
account: user account
Output:
Gives you list of objects, each object corresponds to position of every asset.
token -> Asset info
amount -> collateral locked in position + open orders for this asset + unrealized PnL (can be positive or negative)
value → Number of BTC tokens (locked in position + tokens that will be obtained on fulfilling open orders)
To get Order Book State, we can use the following end points:
Get Index Token, from “Get All Traded Assets endpoint” for each traded asset. For eg: for BTC, indexToken = “0x152b9d0fdc40c096757f570a51e494bd4b943e50”
To connect with websockets, we need to create a initial connection, then connect to /topic/orderBookState:
const socket = new SockJS(URL, {}, {});
stompClient = Stomp.over(socket);
// Subscribe to a single topic that broadcasts all necessary data
stompClient.send("/app/init", {}, indexToken);
stompClient.subscribe("/topic/orderBookState", (message) => {
const data: MarketStateDataWS = JSON.parse(message.body);
});
Get Index Token, from “Get All Traded Assets endpoint” for each traded asset. For eg: for BTC, indexToken = “0x152b9d0fdc40c096757f570a51e494bd4b943e50”
To connect with web sockets, we need to create a initial connection, then connect to /topic/livefeed:
stompClient.send("/app/init", {}, indexToken);
stompClient.subscribe("/topic/livefeed", (message: any) => {
const data = JSON.parse(message.body);
console.log("Received data from WebSocket", data);
if (!data || data.symbol !== tokenCurrency) {
console.log("Invalid data", data);
return;
}
Native WebSocket connections provide a more granular approach to interacting with WebSocket servers without relying on higher-level libraries like SockJS or STOMP clients. Below is an example of connecting to the Filament Finance Order Book WebSocket server using Python.
WebSocket Connection Workflow
Establish a WebSocket Connection
Connect to the WebSocket endpoint using the websocket.WebSocketApp library.
Define callback functions to handle on_open, on_message, on_close, and on_error events.
Send STOMP Frames
Connect Frame: Establishes the STOMP connection by sending necessary headers.
Subscribe Frame: Subscribes to the desired topic, such as /topic/orderBookState.
Send Frame: Sends initialization messages with the required payload (e.g., indexToken).
Receive and Process Messages
Handle incoming messages via the on_message callback to process data in real-time.
Threaded Connection
Run the WebSocket client in a separate thread to maintain a persistent connection while allowing other processes to run concurrently.
import websocket
import threading
import time
# STOMP frame for connecting
def create_connect_frame():
return (
"CONNECT\n"
"accept-version:1.1,1.2\n"
"host:orderbook.filament.finance\n"
"\n"
"\x00"
)
# STOMP frame for subscribing to a topic
def create_subscribe_frame():
return (
"SUBSCRIBE\n"
"id:sub-0\n"
"destination:/topic/orderBookState\n"
"\n"
"\x00"
)
# STOMP frame for sending a message
def create_send_frame(index_token):
return (
f"SEND\ndestination:/app/init\n\n{index_token}\x00"
)
# Define the WebSocket connection URL
ws_url = "wss://orderbook.filament.finance/sei/api/order-book/book-websocket"
# Function to handle messages from the WebSocket
def on_message(ws, message):
print(f"Received message: {message}")
# Function to handle WebSocket connection opening
def on_open(ws):
print("WebSocket connection opened")
# Send the STOMP CONNECT frame
ws.send(create_connect_frame())
time.sleep(1) # Small delay to ensure the server processes the connect
# Subscribe to the topic
ws.send(create_subscribe_frame())
print("Subscribed to /topic/orderBookState")
# Send the initialization message
index_token = "0x152b9d0fdc40c096757f570a51e494bd4b943e50"
ws.send(create_send_frame(index_token))
print(f"Sent initialization message with token: {index_token}")
# Function to handle WebSocket closing
def on_close(ws):
print("WebSocket connection closed")
# Function to handle WebSocket errors
def on_error(ws, error):
print(f"WebSocket error: {error}")
# Create the WebSocket app and assign event handlers
ws = websocket.WebSocketApp(ws_url,
on_message=on_message,
on_open=on_open,
on_close=on_close,
on_error=on_error)
# Run the WebSocket in a separate thread
ws_thread = threading.Thread(target=ws.run_forever)
ws_thread.start()
# Keep the main thread alive to maintain the WebSocket connection
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Closing connection")
ws.close()
ws_thread.join()
import websocket
import threading
import time
import json
# STOMP frame for connecting
def create_connect_frame():
return (
"CONNECT\n"
"accept-version:1.1,1.2\n"
"host:orderbook.filament.finance\n"
"\n"
"\x00"
)
# STOMP frame for subscribing to a specific topic
def create_subscribe_frame(account):
return (
f"SUBSCRIBE\n"
f"id:sub-0\n"
f"destination:/topic/order-updates/{account}\n"
"\n"
"\x00"
)
# STOMP frame for sending an initialization message
def create_send_frame(account):
payload = json.dumps({"account": account})
return (
f"SEND\n"
f"destination:/app/init/order-updates\n"
f"content-type:text/plain\n"
f"\n"
f"{payload}\x00"
)
# Define the WebSocket connection URL
ws_url = "wss://orderbook.filament.finance/sei/api/order-websocket"
# Function to handle messages from the WebSocket
def on_message(ws, message):
print(f"Received raw message:\n{message}")
try:
# Extract and parse the JSON body from the STOMP message
if "\n\n" in message:
body = message.split("\n\n", 1)[1].strip("\x00") # Extract message body
data = json.loads(body) # Parse JSON
else:
body = message.strip("\x00")
data = json.loads(body)
# Distinguish between list and single object
if isinstance(data, list):
print("Received initial list of orders:")
for order in data:
print(order)
elif isinstance(data, dict):
print("Received individual order update:")
print(data)
else:
print("Unexpected message format")
except json.JSONDecodeError as e:
print(f"Error parsing JSON message: {e}")
except Exception as e:
print(f"Unexpected error in on_message: {e}")
# Function to handle WebSocket connection opening
def on_open(ws):
print("WebSocket connection opened")
# Send the STOMP CONNECT frame
ws.send(create_connect_frame())
time.sleep(1) # Allow server to process the connection
# Account to subscribe to and initialize
account = "0xb02a810c5bb22b093ed52e3d6adb45c8519d68e7"
# Subscribe to the topic
ws.send(create_subscribe_frame(account))
print(f"Subscribed to /topic/order-updates/{account}")
# Send the initialization message
ws.send(create_send_frame(account))
print(f"Sent initialization message for account: {account}")
# Function to handle WebSocket closing
def on_close(ws):
print("WebSocket connection closed")
# Function to handle WebSocket errors
def on_error(ws, error):
print(f"WebSocket error: {error}")
# Function to handle WebSocket ping messages
def on_ping(ws, message):
print(f"Received ping: {message}")
# Function to handle WebSocket pong messages
def on_pong(ws, message):
print(f"Received pong: {message}")
# Create the WebSocket app and assign event handlers
ws = websocket.WebSocketApp(
ws_url,
on_message=on_message,
on_open=on_open,
on_close=on_close,
on_error=on_error,
on_ping=on_ping,
on_pong=on_pong,
)
# Run the WebSocket in a separate thread
ws_thread = threading.Thread(target=ws.run_forever)
ws_thread.daemon = True # Set as a daemon thread to exit with the main program
ws_thread.start()
# Keep the main thread alive to maintain the WebSocket connection
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Closing connection")
ws.close()
ws_thread.join()
Using the response from the trades endpoint, you can determine the necessary details (quantity, entryPrice, and side) for each position and decide the price at which they want to close the position, either for profit or loss. Below is a step-by-step guide: