diff --git a/server/address_normalization_test.go b/server/address_normalization_test.go new file mode 100644 index 0000000..a32184e --- /dev/null +++ b/server/address_normalization_test.go @@ -0,0 +1,71 @@ +package server + +import ( + "testing" +) + +// TestNormalizeAddress verifies address normalization for case-insensitive comparison +func TestNormalizeAddress(t *testing.T) { + tests := []struct { + name string + addr1 string + addr2 string + expected bool // should they be equal after normalization? + }{ + { + name: "identical lowercase addresses", + addr1: "0xeb5df7323c643f01b8c0643be808a0e6486621e8", + addr2: "0xeb5df7323c643f01b8c0643be808a0e6486621e8", + expected: true, + }, + { + name: "identical uppercase addresses", + addr1: "0XEB5DF7323C643F01B8C0643BE808A0E6486621E8", + addr2: "0XEB5DF7323C643F01B8C0643BE808A0E6486621E8", + expected: true, + }, + { + name: "mixed case vs lowercase (EIP-55 checksum)", + addr1: "0xeb5Df7323c643f01b8C0643bE808a0e6486621e8", // checksummed + addr2: "0xeb5df7323c643f01b8c0643be808a0e6486621e8", // lowercase + expected: true, + }, + { + name: "mixed case vs uppercase", + addr1: "0xeb5Df7323c643f01b8C0643bE808a0e6486621e8", // checksummed + addr2: "0XEB5DF7323C643F01B8C0643BE808A0E6486621E8", // uppercase + expected: true, + }, + { + name: "different addresses (lowercase)", + addr1: "0xeb5df7323c643f01b8c0643be808a0e6486621e8", + addr2: "0x628f0be408bdf24451a1c30c452abbb9cfb50a18", + expected: false, + }, + { + name: "different addresses (mixed case)", + addr1: "0xeb5Df7323c643f01b8C0643bE808a0e6486621e8", + addr2: "0x628f0bE408bdf24451a1c30C452abbB9cfb50A18", + expected: false, + }, + { + name: "empty addresses", + addr1: "", + addr2: "", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + norm1 := normalizeAddress(tt.addr1) + norm2 := normalizeAddress(tt.addr2) + equal := norm1 == norm2 + + if equal != tt.expected { + t.Errorf("normalizeAddress(%q) vs normalizeAddress(%q):\ngot equal=%v, want equal=%v\nnorm1=%q, norm2=%q", + tt.addr1, tt.addr2, equal, tt.expected, norm1, norm2) + } + }) + } +} diff --git a/server/handlers.go b/server/handlers.go index 1dbaa9e..14ebfcc 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -10,6 +10,12 @@ import ( "time" ) +// normalizeAddress converts an Ethereum address to lowercase for case-insensitive comparison. +// Ethereum addresses are case-insensitive; the mixed case (EIP-55 checksum) is optional validation. +func normalizeAddress(addr string) string { + return strings.ToLower(addr) +} + // Handler manages HTTP requests for the mock server type Handler struct { state *State @@ -546,7 +552,9 @@ func (h *Handler) handleOrderStatus(req InfoRequest) OrderQueryResult { return OrderQueryResult{Status: "unknown_cloid"} } - if order.Order.User != req.User { + // Compare normalized addresses for case-insensitive matching + // Ethereum addresses are case-insensitive; EIP-55 checksum is optional + if normalizeAddress(order.Order.User) != normalizeAddress(req.User) { h.logger.Debug("order present but user does not match", slog.String("order.User", order.Order.User), slog.String("req.User", req.User)) return OrderQueryResult{Status: "unknown_cloid"} } diff --git a/server/websocket.go b/server/websocket.go index 1f0af8d..12859d6 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -196,11 +196,13 @@ func (wsm *WebSocketManager) handleSubscribe(state *ConnectionState, sub map[str wsm.sendError(state.conn, "Missing user address for orderUpdates") return } + // Normalize address for case-insensitive comparison + normalizedUser := normalizeAddress(user) state.mu.Lock() - state.orderUpdatesUser = user + state.orderUpdatesUser = normalizedUser state.mu.Unlock() - wsm.logger.Info("subscribed to orderUpdates", "user", user) + wsm.logger.Info("subscribed to orderUpdates", "user", user, "normalized", normalizedUser) // Send subscription acknowledgment wsm.sendSubscriptionResponse(state.conn, sub) @@ -370,7 +372,8 @@ func (wsm *WebSocketManager) broadcastOrderUpdates() { // Send if: // 1. Order has no wallet (signature recovery failed) - backward compatibility // 2. Order wallet matches subscriber wallet - proper isolation - if orderUser == "" || orderUser == subscribedUser { + // (Compare normalized addresses for case-insensitive matching) + if orderUser == "" || normalizeAddress(orderUser) == subscribedUser { shouldSend = true } }