- Backend REST/gateway/track routes, analytics, Blockscout proxy paths. - Frontend wallet and liquidity surfaces; MetaMask token list alignment. - Deployment docs, verification scripts, address inventory updates. Check: go build ./... under backend/ (pass). Made-with: Cursor
110 lines
2.5 KiB
Go
110 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
|
|
}
|
|
if !s.requireDB(w) {
|
|
return
|
|
}
|
|
|
|
// Parse address from URL
|
|
address := normalizeAddress(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 (LOWER(from_address) = $2 OR LOWER(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_contract) FROM token_transfers WHERE chain_id = $1 AND (LOWER(from_address) = $2 OR LOWER(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 LOWER(address) = $2 AND label_type = 'public' LIMIT 1`,
|
|
s.chainID, address,
|
|
).Scan(&label)
|
|
|
|
// Get tags
|
|
rows, err := s.db.Query(ctx,
|
|
`SELECT tag FROM address_tags WHERE chain_id = $1 AND LOWER(address) = $2`,
|
|
s.chainID, address,
|
|
)
|
|
tags := []string{}
|
|
if err == nil {
|
|
defer rows.Close()
|
|
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 LOWER(address) = $2)`,
|
|
s.chainID, address,
|
|
).Scan(&isContract)
|
|
|
|
response := map[string]interface{}{
|
|
"address": address,
|
|
"chain_id": s.chainID,
|
|
"balance": nil,
|
|
"balance_unavailable": true,
|
|
"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,
|
|
})
|
|
}
|