main.py.2 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. from fastapi import FastAPI, WebSocket, WebSocketDisconnect
  2. from fastapi.middleware.cors import CORSMiddleware
  3. from fastapi import Body
  4. from dotenv import load_dotenv
  5. import os
  6. import json
  7. import asyncio
  8. from typing import List, Dict, Any
  9. from hyperliquid.info import Info
  10. from hyperliquid.exchange import Exchange
  11. from hyperliquid.utils import constants
  12. from hyperliquid.utils.signing import get_timestamp_ms
  13. from eth_account import Account
  14. # 加载 .env 文件
  15. load_dotenv()
  16. # Testnet 配置
  17. TESTNET_API_URL = constants.TESTNET_API_URL
  18. # 初始化全局变量
  19. info = None
  20. exchange = None
  21. app = FastAPI(title="Hyperliquid DEX Backend")
  22. # 添加 CORS 中间件(允许前端访问)
  23. app.add_middleware(
  24. CORSMiddleware,
  25. allow_origins=["http://localhost:5173", "http://101.32.51.95:5173"], # 前端 URL,根据需要调整
  26. allow_credentials=True,
  27. allow_methods=["*"],
  28. allow_headers=["*"],
  29. )
  30. # 启动事件:初始化 SDK
  31. @app.on_event("startup")
  32. async def startup_event():
  33. global info, exchange
  34. info = Info(TESTNET_API_URL, skip_ws=False)
  35. # 初始化 Exchange,使用你的代理私钥
  36. private_key = os.getenv("PRIVATE_KEY")
  37. if private_key:
  38. account = Account.from_key(private_key)
  39. exchange = Exchange(TESTNET_API_URL, account=account, skip_ws=True)
  40. else:
  41. raise ValueError("PRIVATE_KEY 未设置,请检查 .env 文件")
  42. # WebSocket 管理器
  43. class ConnectionManager:
  44. def __init__(self):
  45. self.active_connections: List[WebSocket] = []
  46. async def connect(self, websocket: WebSocket):
  47. await websocket.accept()
  48. self.active_connections.append(websocket)
  49. def disconnect(self, websocket: WebSocket):
  50. self.active_connections.remove(websocket)
  51. async def broadcast(self, data: Dict[str, Any]):
  52. for connection in self.active_connections:
  53. await connection.send_json(data)
  54. manager = ConnectionManager()
  55. # 根路径
  56. @app.get("/")
  57. def read_root():
  58. return {"message": "Hyperliquid DEX Backend 就绪!"}
  59. # 健康检查
  60. @app.get("/health")
  61. def health_check():
  62. return {
  63. "status": "OK",
  64. "builder_address": os.getenv("BUILDER_ADDRESS"),
  65. "max_fee_rate": os.getenv("MAX_FEE_RATE")
  66. }
  67. # 查询价格
  68. @app.get("/prices")
  69. def get_prices():
  70. global info
  71. if info:
  72. mids = info.all_mids()
  73. return {"prices": mids}
  74. return {"error": "SDK 未初始化"}
  75. # 批准 Builder Fee API
  76. @app.post("/approve-builder")
  77. async def approve_builder(request: Dict[str, Any] = Body(...)):
  78. """
  79. 用户批准 Builder Fee:签名批准你的地址的最大费率。
  80. Body 示例: {"user_address": "0xUserAddress", "max_fee_rate": 50}
  81. """
  82. try:
  83. user_address = request.get("user_address")
  84. max_fee_rate = request.get("max_fee_rate", int(os.getenv("MAX_FEE_RATE", 50)))
  85. if not user_address:
  86. return {"error": "user_address 必填"}
  87. # 构建批准消息
  88. timestamp = get_timestamp_ms()
  89. action = {
  90. "type": "BuilderApprove",
  91. "builder": os.getenv("BUILDER_ADDRESS"),
  92. "maxBuilderFeeBps": max_fee_rate * 10, # 转换为 bps (0.05% = 50 * 10 = 500 bps)
  93. "nonce": timestamp,
  94. }
  95. # 签名并提交(实际中,用户签名在前端,后端验证并提交)
  96. # 这里简化使用代理账户签名,生产中需用户提供签名
  97. signed_action = exchange.sign_action(action, user_address)
  98. result = exchange.builder_approve(signed_action)
  99. return {
  100. "status": "批准成功",
  101. "result": result,
  102. "fee_rate": max_fee_rate,
  103. "builder_address": os.getenv("BUILDER_ADDRESS")
  104. }
  105. except Exception as e:
  106. return {"error": str(e)}
  107. # 下单 API
  108. @app.post("/place-order")
  109. async def place_order(request: Dict[str, Any] = Body(...)):
  110. """
  111. 下单 API:注入 Builder Fee。
  112. Body 示例: {
  113. "asset": "BTC",
  114. "is_buy": true,
  115. "sz": 0.01,
  116. "limit_px": 60000,
  117. "user_address": "0xUserAddress"
  118. }
  119. """
  120. try:
  121. order_data = request.copy()
  122. user_address = order_data.get("user_address")
  123. if not user_address:
  124. return {"error": "user_address 必填"}
  125. # 注入 Builder
  126. order_data["builder"] = {
  127. "address": os.getenv("BUILDER_ADDRESS"),
  128. "fee": int(os.getenv("MAX_FEE_RATE", 50)),
  129. }
  130. # 签名并提交(简化:实际用户签名在前端)
  131. signed_order = exchange.sign_order(order_data, user_address)
  132. result = exchange.place_order(signed_order)
  133. # 广播结果(WebSocket)
  134. await manager.broadcast({"order_result": result})
  135. return {"status": "下单成功", "result": result}
  136. except Exception as e:
  137. return {"error": str(e)}
  138. # 查询 Builder 收益
  139. @app.get("/builder-fees")
  140. def get_builder_fees():
  141. """
  142. 查询你的 Builder 费率和估算收益(简化,实际用 SDK 查询 fills)
  143. """
  144. try:
  145. # 示例:用 Info 查询用户状态(需扩展为 fills 查询)
  146. user_state = info.user_state(os.getenv("BUILDER_ADDRESS"))
  147. return {
  148. "max_fee_rate": os.getenv("MAX_FEE_RATE"),
  149. "user_state": user_state,
  150. "estimated_earnings": "使用 Hyperliquid Stats 下载 fills CSV 查看实际收益"
  151. }
  152. except Exception as e:
  153. return {"error": str(e)}
  154. # WebSocket 端点:实时推送订单簿和价格
  155. @app.websocket("/ws")
  156. async def websocket_endpoint(websocket: WebSocket):
  157. await manager.connect(websocket)
  158. try:
  159. while True:
  160. global info
  161. if info:
  162. data = {
  163. "orderbook": info.l2_book("BTC"), # 示例:BTC L2 订单簿
  164. "prices": info.all_mids(),
  165. "timestamp": get_timestamp_ms(),
  166. }
  167. await manager.broadcast(data)
  168. await asyncio.sleep(5) # 每 5 秒推送一次
  169. except WebSocketDisconnect:
  170. manager.disconnect(websocket)
  171. except Exception as e:
  172. print(f"WebSocket 错误: {e}")
  173. if __name__ == "__main__":
  174. import uvicorn
  175. uvicorn.run(app, host="0.0.0.0", port=8000, reload=True)