Files
explorer-monorepo/backend/api/rest/addresses.go
defiQUG a53c15507f fix: API JSON error responses + navbar with dropdowns
- Add backend/libs/go-http-errors for consistent JSON errors
- REST API: use writeMethodNotAllowed, writeNotFound, writeInternalError
- middleware, gateway, search: use httperrors.WriteJSON
- SPA: navbar with Explore/Tools/More dropdowns, initNavDropdowns()
- Next.js: Navbar component with dropdowns + mobile menu

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 03:09:53 -08:00

109 lines
2.5 KiB
Go

package rest
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"net/http"
"time"
)
// handleGetAddress handles GET /api/v1/addresses/{chain_id}/{address}
func (s *Server) handleGetAddress(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeMethodNotAllowed(w)
return
}
// Parse address from URL
address := r.URL.Query().Get("address")
if address == "" {
writeValidationError(w, fmt.Errorf("address required"))
return
}
// Validate address format
if !isValidAddress(address) {
writeValidationError(w, ErrInvalidAddress)
return
}
// Add query timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Get transaction count
var txCount int64
err := s.db.QueryRow(ctx,
`SELECT COUNT(*) FROM transactions WHERE chain_id = $1 AND (from_address = $2 OR to_address = $2)`,
s.chainID, address,
).Scan(&txCount)
if err != nil {
writeInternalError(w, "Database error")
return
}
// Get token count
var tokenCount int
err = s.db.QueryRow(ctx,
`SELECT COUNT(DISTINCT token_address) FROM token_transfers WHERE chain_id = $1 AND (from_address = $2 OR to_address = $2)`,
s.chainID, address,
).Scan(&tokenCount)
if err != nil {
tokenCount = 0
}
// Get label
var label sql.NullString
s.db.QueryRow(ctx,
`SELECT label FROM address_labels WHERE chain_id = $1 AND address = $2 AND label_type = 'public' LIMIT 1`,
s.chainID, address,
).Scan(&label)
// Get tags
rows, _ := s.db.Query(ctx,
`SELECT tag FROM address_tags WHERE chain_id = $1 AND address = $2`,
s.chainID, address,
)
defer rows.Close()
tags := []string{}
for rows.Next() {
var tag string
if err := rows.Scan(&tag); err == nil {
tags = append(tags, tag)
}
}
// Check if contract
var isContract bool
s.db.QueryRow(ctx,
`SELECT EXISTS(SELECT 1 FROM contracts WHERE chain_id = $1 AND address = $2)`,
s.chainID, address,
).Scan(&isContract)
// Get balance (if we have RPC access, otherwise 0)
balance := "0"
// TODO: Add RPC call to get balance if needed
response := map[string]interface{}{
"address": address,
"chain_id": s.chainID,
"balance": balance,
"transaction_count": txCount,
"token_count": tokenCount,
"is_contract": isContract,
"tags": tags,
}
if label.Valid {
response["label"] = label.String
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"data": response,
})
}