- 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
86 lines
2.2 KiB
Go
86 lines
2.2 KiB
Go
package rest
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type explorerStats struct {
|
|
TotalBlocks int64 `json:"total_blocks"`
|
|
TotalTransactions int64 `json:"total_transactions"`
|
|
TotalAddresses int64 `json:"total_addresses"`
|
|
LatestBlock int64 `json:"latest_block"`
|
|
}
|
|
|
|
type statsQueryFunc func(ctx context.Context, sql string, args ...any) pgx.Row
|
|
|
|
func loadExplorerStats(ctx context.Context, chainID int, queryRow statsQueryFunc) (explorerStats, error) {
|
|
var stats explorerStats
|
|
|
|
if err := queryRow(ctx,
|
|
`SELECT COUNT(*) FROM blocks WHERE chain_id = $1`,
|
|
chainID,
|
|
).Scan(&stats.TotalBlocks); err != nil {
|
|
return explorerStats{}, fmt.Errorf("query total blocks: %w", err)
|
|
}
|
|
|
|
if err := queryRow(ctx,
|
|
`SELECT COUNT(*) FROM transactions WHERE chain_id = $1`,
|
|
chainID,
|
|
).Scan(&stats.TotalTransactions); err != nil {
|
|
return explorerStats{}, fmt.Errorf("query total transactions: %w", err)
|
|
}
|
|
|
|
if err := queryRow(ctx,
|
|
`SELECT COUNT(*) FROM (
|
|
SELECT from_address AS address
|
|
FROM transactions
|
|
WHERE chain_id = $1 AND from_address IS NOT NULL AND from_address <> ''
|
|
UNION
|
|
SELECT to_address AS address
|
|
FROM transactions
|
|
WHERE chain_id = $1 AND to_address IS NOT NULL AND to_address <> ''
|
|
) unique_addresses`,
|
|
chainID,
|
|
).Scan(&stats.TotalAddresses); err != nil {
|
|
return explorerStats{}, fmt.Errorf("query total addresses: %w", err)
|
|
}
|
|
|
|
if err := queryRow(ctx,
|
|
`SELECT COALESCE(MAX(number), 0) FROM blocks WHERE chain_id = $1`,
|
|
chainID,
|
|
).Scan(&stats.LatestBlock); err != nil {
|
|
return explorerStats{}, fmt.Errorf("query latest block: %w", err)
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// handleStats handles GET /api/v2/stats
|
|
func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
writeMethodNotAllowed(w)
|
|
return
|
|
}
|
|
if !s.requireDB(w) {
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
stats, err := loadExplorerStats(ctx, s.chainID, s.db.QueryRow)
|
|
if err != nil {
|
|
writeError(w, http.StatusServiceUnavailable, "service_unavailable", "explorer stats are temporarily unavailable")
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(stats)
|
|
}
|