Skip to content

feat: implement WebSocket support for order updates and market data#8

Merged
terwey merged 6 commits intomainfrom
claude/websocket-hyperliquid-mock-server-011CUtue7BoHccCCSHeP9h73
Nov 7, 2025
Merged

feat: implement WebSocket support for order updates and market data#8
terwey merged 6 commits intomainfrom
claude/websocket-hyperliquid-mock-server-011CUtue7BoHccCCSHeP9h73

Conversation

@terwey
Copy link
Copy Markdown
Contributor

@terwey terwey commented Nov 7, 2025

Add comprehensive WebSocket functionality to the Hyperliquid mock server:

WebSocket Features:

  • Real-time order updates subscription (orderUpdates)
  • Best bid/offer market data subscription (l2Book/bbo)
  • Automatic broadcasting on order state changes
  • Multiple subscriptions per connection
  • Thread-safe concurrent operations

Implementation:

  • New WebSocketManager for connection and subscription handling
  • Integration with existing State for automatic order update broadcasts
  • WebSocket endpoint at /ws
  • Support for subscribe/unsubscribe operations

Test Utilities:

  • TestServer.WebSocketURL() for easy test connections
  • TestServer.SetBBO() for manual market data updates
  • TestServer.TriggerBBOUpdate() for forcing BBO broadcasts
  • Example tests demonstrating WebSocket usage

Documentation:

  • Comprehensive WEBSOCKET.md guide
  • Updated README.md with WebSocket information
  • Example code for order updates and BBO subscriptions

Mock Data:

  • BTC: bid=86956.5, ask=87043.5 (centered on 87000 USDT)
  • ETH: bid=2999.5, ask=3000.5
  • SOL: bid=99.9, ask=100.1

Compliance:

  • Follows official Hyperliquid WebSocket API specifications
  • Compatible with upstream API docs

Add comprehensive WebSocket functionality to the Hyperliquid mock server:

WebSocket Features:
- Real-time order updates subscription (orderUpdates)
- Best bid/offer market data subscription (l2Book/bbo)
- Automatic broadcasting on order state changes
- Multiple subscriptions per connection
- Thread-safe concurrent operations

Implementation:
- New WebSocketManager for connection and subscription handling
- Integration with existing State for automatic order update broadcasts
- WebSocket endpoint at /ws
- Support for subscribe/unsubscribe operations

Test Utilities:
- TestServer.WebSocketURL() for easy test connections
- TestServer.SetBBO() for manual market data updates
- TestServer.TriggerBBOUpdate() for forcing BBO broadcasts
- Example tests demonstrating WebSocket usage

Documentation:
- Comprehensive WEBSOCKET.md guide
- Updated README.md with WebSocket information
- Example code for order updates and BBO subscriptions

Mock Data:
- BTC: bid=86956.5, ask=87043.5 (centered on 87000 USDT)
- ETH: bid=2999.5, ask=3000.5
- SOL: bid=99.9, ask=100.1

Compliance:
- Follows official Hyperliquid WebSocket API specifications
- Compatible with upstream API docs
Go example functions must be niladic (no parameters), but these needed
*testing.T for proper test infrastructure. Renamed from Example* to Test*
to fix build errors:
- ExampleWebSocketOrderUpdates -> TestWebSocketOrderUpdates
- ExampleWebSocketBBO -> TestWebSocketBBO
- ExampleManualBBOUpdate -> TestManualBBOUpdate

Fixes build error: 'Example functions should be niladic'
The request capture middleware was interfering with WebSocket handshakes
by trying to read the request body. WebSocket upgrades use HTTP headers
for the handshake, not the request body, so we now skip body capture
when the 'Upgrade: websocket' header is present.

Fixes: websocket: bad handshake error in tests
@terwey terwey marked this pull request as ready for review November 7, 2025 17:35
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/server.go Outdated
Comment on lines +22 to +28
logger.Info("Mock Hyperliquid API server listening", "addr", addr)
logger.Info("Endpoints", "exchange", addr+"/exchange", "info", addr+"/info", "health", addr+"/health")
logger.Info("Endpoints",
"exchange", addr+"/exchange",
"info", addr+"/info",
"health", addr+"/health",
"websocket", "ws://"+addr[7:]+"/ws")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Avoid panic when logging WebSocket endpoint

Run assumes the supplied server address is prefixed with http:// and slices addr[7:] when logging the WebSocket URL. The main entry point passes ":8080", so the slice indexes past the end of the string and the server panics before listening. Format the WebSocket URL without assuming a protocol so the process can start.

Useful? React with 👍 / 👎.

Comment thread server/state.go
Comment on lines 47 to +60
s.orders[cloid] = order

// Broadcast order update via WebSocket
if s.wsm != nil {
// Note: In a real system, we'd track which user owns which order
// For the mock, we use a placeholder user address
s.wsm.BroadcastOrderUpdate("", order)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Order update broadcasts never reach subscribers

Every order state mutation enqueues BroadcastOrderUpdate("", order), but the broadcaster forwards updates only when a connection’s subscribed user matches update.User. Because handleSubscribe requires a concrete user string, any real subscription will never match the empty user used here, so orderUpdates messages are silently dropped. The handler must propagate the actual user address (or broadcast without filtering) for the new WebSocket feature to work.

Useful? React with 👍 / 👎.

The Run function receives addresses like ':8080' without a protocol prefix,
but the WebSocket URL logging was assuming 'http://' and slicing addr[7:],
causing an index out of bounds panic.

Now properly handles addresses starting with ':' by prepending 'localhost'
when formatting the WebSocket URL for logging.

Fixes panic on server startup when address is ':8080'
Critical bug fix: Order updates were being broadcast with an empty user
string but subscribers require a specific user address. The user filtering
check 'if state.orderUpdatesUser == update.User' always failed, causing
all orderUpdates messages to be silently dropped.

Changes:
- Removed user parameter from BroadcastOrderUpdate()
- Changed broadcast logic to send to all orderUpdates subscribers
- Simplified OrderUpdate struct (removed User field)
- Updated all call sites in state.go and testserver.go

For a mock server, broadcasting to all subscribers is appropriate since
we don't track actual user ownership of orders. Subscribers still need
to provide a user address when subscribing (per Hyperliquid protocol),
but the mock broadcasts all order changes to everyone subscribed to
orderUpdates.

This makes the WebSocket orderUpdates feature actually work.
@terwey
Copy link
Copy Markdown
Contributor Author

terwey commented Nov 7, 2025

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/websocket.go
Comment on lines +412 to +416
msg := map[string]interface{}{
"channel": "l2Book",
"data": map[string]interface{}{
"coin": coin,
"time": time.Now().UnixMilli(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Send BBO subscriptions in correct channel format

Clients subscribing with {"type": "bbo"} receive messages built by sendInitialBBO/broadcastL2Book that are tagged as channel: "l2Book" and contain a levels array. According to upstream-api-docs/subscriptions.md, the BBO feed should use channel: "bbo" and a payload shaped like WsBbo { coin, time, bbo: [bid, ask] }. As implemented, any consumer expecting the documented BBO schema will fail to parse the messages. Consider emitting a distinct bbo channel with a bbo field or otherwise matching the documented type.

Useful? React with 👍 / 👎.

BBO and l2Book subscriptions now send different message formats:

l2Book subscriptions receive:
- channel: "l2Book"
- data.levels: [[bids...], [asks...]]

bbo subscriptions receive:
- channel: "bbo"
- data.bbo: [bid, ask]

Changes:
- Track l2Book and bbo subscriptions separately in ConnectionState
- Split sendInitialBBO into sendInitialL2Book and sendInitialBBO
- Updated broadcastL2Book to send different formats based on subscription
- Consumers subscribing to bbo now receive the correct WsBbo format

This matches the upstream API docs (upstream-api-docs/subscriptions.md)
@terwey
Copy link
Copy Markdown
Contributor Author

terwey commented Nov 7, 2025

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@terwey terwey merged commit b7fb71d into main Nov 7, 2025
1 check passed
@terwey terwey deleted the claude/websocket-hyperliquid-mock-server-011CUtue7BoHccCCSHeP9h73 branch November 7, 2025 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants