In this tutorial, we'll guide you through building a liquidation bot forZest, a lending protocol built on Stacks. We'll focus on how to connect to Ortege's API using WebSockets and the REST syntax to receive real-time data. Specifically, we'll use the newly added stacks_contract_events cube to monitor contract events related to Zest's protocol. We'll show you how to filter by contract_id and function_name to target the events you're interested in.
Table of Contents
Prerequisites
Understanding WebSockets with REST Syntax
API Configuration
The stacks_contract_events Cube
Setting Up the Subscription Query
Python Implementation
JavaScript Implementation
Conclusion
Prerequisites
Basic understanding of WebSockets and REST APIs
Familiarity with Python or JavaScript
Access to Ortege's API (Ensure you have your JWT token)
Familiarity with Stacks and Zest protocol concepts
Understanding WebSockets with REST Syntax
WebSockets provide a persistent, full-duplex communication channel between a client and a server. By sending REST-style queries over WebSockets, you can receive real-time updates without the overhead of repeated HTTP requests.
This method allows you to:
Maintain a persistent connection for real-time data.
We've added a new cube called stacks_contract_events to facilitate access to contract event data from the Stacks blockchain. This cube allows you to monitor events emitted by smart contracts, which is essential for building a liquidation bot that responds to specific actions like withdraw, supply, or borrow in the Zest protocol.
We're summing the asset_amount to monitor the total assets involved.
Dimensions:
contract_id, function_name, sender_address, tx_date provide context for each event.
Filters:
Contract ID Filter: Ensures we only receive events from the Zest protocol's contract.
Function Name Filter: Limits events to the specified functions (withdraw, supply, borrow).
Python Implementation
Installation
Install the required Python packages:
pipinstallwebsocket-client
Code Example
import jsonimport threadingimport timeimport uuidfrom websocket import WebSocketApp# Configuration ParametersWEBSOCKET_URL ="wss://api-staging.ortege.ai/cubejs-api/v1/load/"jwt_token ="YOUR_JWT_TOKEN"# Replace with your actual JWT tokendefexample_websocket_query():""" Constructs the REST-style subscription query to be sent over WebSocket. """ query_payload ={"queryType":"subscribe","query":{"measures": ["stacks_contract_events.asset_amount"],"timeDimensions": [],"dimensions": ["stacks_contract_events.contract_id","stacks_contract_events.function_name","stacks_contract_events.sender_address","stacks_contract_events.tx_date" ],"filters": [{"dimension":"stacks_contract_events.contract_id","operator":"equals","values": ["SP2VCQJGH7PHP2DJK7Z0V48AGBHQAW3R3ZW1QF4N.borrow-helper-v1-3"]},{"dimension":"stacks_contract_events.function_name","operator":"equals","values": ["withdraw","supply","borrow"]} ]}}return query_payloaddefon_message(ws,message):""" Callback when a message is received from the WebSocket. """print("\n--- WebSocket Message Received ---")try: parsed_message = json.loads(message)print(json.dumps(parsed_message, indent=2))# Add your liquidation logic here# For example:# for event in parsed_message.get('data', []):# process_event(event)except json.JSONDecodeError:print(message)defon_error(ws,error):""" Callback when an error occurs on the WebSocket. """print(f"\nWebSocket Error: {error}")defon_close(ws,close_status_code,close_msg):""" Callback when the WebSocket connection is closed. """print("\nWebSocket Connection Closed")defon_open(ws):""" Callback when the WebSocket connection is opened. Sends the subscription query. """print("\nWebSocket Connection Opened")# Generate a unique requestId using UUID4 request_id =str(uuid.uuid4())# Prepare the subscription message query_payload =example_websocket_query() query_payload['requestId']= request_id# Send the subscription message ws.send(json.dumps(query_payload))print(f"WebSocket Subscription Query Sent with requestId: {request_id}")defwebsocket_api_query():""" Executes a WebSocket API subscription query against Ortegr's API. """ headers ={"Authorization": f"Bearer {jwt_token}"} ws =WebSocketApp( WEBSOCKET_URL, header=headers, on_open=on_open, on_message=on_message, on_error=on_error, on_close=on_close )# Run WebSocket in a separate thread to prevent blocking wst = threading.Thread(target=ws.run_forever, kwargs={"ping_interval": 60, "ping_timeout": 10}) wst.daemon =True wst.start()print("WebSocket thread started.")# Keep the main thread alive to listen to incoming messagestry:whileTrue: time.sleep(1)exceptKeyboardInterrupt: ws.close()print("\nWebSocket Connection Interrupted and Closed")defmain():try:# Execute the WebSocket API subscription queryprint("\n--- Initiating WebSocket API Subscription ---")websocket_api_query()exceptExceptionas e:print(f"\nWebSocket API Error: {str(e)}")if__name__=="__main__":main()
Explanation
Headers: Include your JWT token in the WebSocket headers.
Subscription Message: Send a REST-style query as a JSON payload over the WebSocket connection.
Callbacks: Implement on_message, on_error, on_close, and on_open to handle WebSocket events.
requestId: A unique identifier for the query, useful for matching responses.
Liquidation Logic: Insert your bot's liquidation logic inside the on_message callback where indicated.
Running the Script
Replace"YOUR_JWT_TOKEN" with your actual JWT token.
Run the script:
pythonyour_script_name.py
JavaScript Implementation
Installation
Install the required Node.js packages:
npminstallwebsocketuuid
Code Example
constWebSocketClient=require('websocket').w3cwebsocket;constuuid=require('uuid');// Configuration ParametersconstWEBSOCKET_URL="wss://api-staging.ortege.ai/cubejs-api/v1/load/";constjwt_token="YOUR_JWT_TOKEN"; // Replace with your actual JWT tokenfunctionexampleWebSocketQuery() {return {"queryType":"subscribe","query": {"measures": ["stacks_contract_events.asset_amount"],"timeDimensions": [],"dimensions": ["stacks_contract_events.contract_id","stacks_contract_events.function_name","stacks_contract_events.sender_address","stacks_contract_events.tx_date" ],"filters": [ {"dimension":"stacks_contract_events.contract_id","operator":"equals","values": ["SP2VCQJGH7PHP2DJK7Z0V48AGBHQAW3R3ZW1QF4N.borrow-helper-v1-3"] }, {"dimension":"stacks_contract_events.function_name","operator":"equals","values": ["withdraw","supply","borrow"] } ] } };}functiononMessage(message) {console.log("\n--- WebSocket Message Received ---");try {constparsedMessage=JSON.parse(message.data);console.log(JSON.stringify(parsedMessage,null,2));// Add your liquidation logic here// For example:// parsedMessage.data.forEach(event => processEvent(event)); } catch (error) {console.log(message.data); }}functiononError(error) {console.error(`\nWebSocket Error: ${error.message}`);}functiononClose() {console.log("\nWebSocket Connection Closed");}functiononOpen() {console.log("\nWebSocket Connection Opened");constrequestId=uuid.v4();constqueryPayload=exampleWebSocketQuery(); queryPayload['requestId'] = requestId;ws.send(JSON.stringify(queryPayload));console.log(`WebSocket Subscription Query Sent with requestId: ${requestId}`);}// WebSocket Connectionconstws=newWebSocketClient(WEBSOCKET_URL,null,null, { 'Authorization':`Bearer ${jwt_token}` });ws.onopen = onOpen;ws.onmessage = onMessage;ws.onerror = onError;ws.onclose = onClose;
Explanation
Headers: Include your JWT token in the WebSocket headers.
Subscription Message: Send a REST-style query as a JSON payload.
Callbacks: Implement onopen, onmessage, onerror, and onclose to handle WebSocket events.
UUID: Use the uuid package to generate a unique requestId.
Liquidation Logic: Insert your bot's logic inside the onMessage function.
Running the Script
Replace"YOUR_JWT_TOKEN" with your actual JWT token.
Run the script:
nodeyour_script_name.js
Conclusion
By integrating the stacks_contract_events cube into your WebSocket subscription, you've tailored your liquidation bot to monitor specific contract events from the Zest protocol. This allows your bot to react promptly to critical events like withdraw, supply, or borrow actions, enhancing its effectiveness.
Next Steps:
Implement Logic: Add your liquidation logic inside the on_message (Python) or onMessage (JavaScript) callback.
Data Processing: Parse the incoming data to extract necessary information for your bot's operations.
Error Handling: Enhance error handling for robustness in production environments.
Security: Ensure your JWT token is stored securely.
Note: Always ensure you comply with all relevant laws and regulations when building and deploying bots or automated systems.