feat: explorer API, wallet, CCIP scripts, and config refresh

- 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
This commit is contained in:
defiQUG
2026-04-07 23:22:12 -07:00
parent d931be8e19
commit 6eef6b07f6
224 changed files with 19671 additions and 3291 deletions

View File

@@ -2,11 +2,13 @@ package track3
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/explorer/backend/analytics"
"github.com/jackc/pgx/v5/pgxpool"
)
@@ -35,9 +37,29 @@ func NewServer(db *pgxpool.Pool, chainID int) *Server {
// HandleFlows handles GET /api/v1/track3/analytics/flows
func (s *Server) HandleFlows(w http.ResponseWriter, r *http.Request) {
from := r.URL.Query().Get("from")
to := r.URL.Query().Get("to")
token := r.URL.Query().Get("token")
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
return
}
if !s.requireDB(w) {
return
}
from, err := normalizeTrack3OptionalAddress(r.URL.Query().Get("from"))
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", err.Error())
return
}
to, err := normalizeTrack3OptionalAddress(r.URL.Query().Get("to"))
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", err.Error())
return
}
token, err := normalizeTrack3OptionalAddress(r.URL.Query().Get("token"))
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", err.Error())
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
if limit < 1 || limit > 200 {
limit = 50
@@ -45,14 +67,20 @@ func (s *Server) HandleFlows(w http.ResponseWriter, r *http.Request) {
var startDate, endDate *time.Time
if startStr := r.URL.Query().Get("start_date"); startStr != "" {
if t, err := time.Parse(time.RFC3339, startStr); err == nil {
startDate = &t
t, err := time.Parse(time.RFC3339, startStr)
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "invalid start_date")
return
}
startDate = &t
}
if endStr := r.URL.Query().Get("end_date"); endStr != "" {
if t, err := time.Parse(time.RFC3339, endStr); err == nil {
endDate = &t
t, err := time.Parse(time.RFC3339, endStr)
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "invalid end_date")
return
}
endDate = &t
}
flows, err := s.flowTracker.GetFlows(r.Context(), from, to, token, startDate, endDate, limit)
@@ -73,28 +101,48 @@ func (s *Server) HandleFlows(w http.ResponseWriter, r *http.Request) {
// HandleBridge handles GET /api/v1/track3/analytics/bridge
func (s *Server) HandleBridge(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
return
}
if !s.requireDB(w) {
return
}
var chainFrom, chainTo *int
if cf := r.URL.Query().Get("chain_from"); cf != "" {
if c, err := strconv.Atoi(cf); err == nil {
chainFrom = &c
c, err := strconv.Atoi(cf)
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "invalid chain_from")
return
}
chainFrom = &c
}
if ct := r.URL.Query().Get("chain_to"); ct != "" {
if c, err := strconv.Atoi(ct); err == nil {
chainTo = &c
c, err := strconv.Atoi(ct)
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "invalid chain_to")
return
}
chainTo = &c
}
var startDate, endDate *time.Time
if startStr := r.URL.Query().Get("start_date"); startStr != "" {
if t, err := time.Parse(time.RFC3339, startStr); err == nil {
startDate = &t
t, err := time.Parse(time.RFC3339, startStr)
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "invalid start_date")
return
}
startDate = &t
}
if endStr := r.URL.Query().Get("end_date"); endStr != "" {
if t, err := time.Parse(time.RFC3339, endStr); err == nil {
endDate = &t
t, err := time.Parse(time.RFC3339, endStr)
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "invalid end_date")
return
}
endDate = &t
}
stats, err := s.bridgeAnalytics.GetBridgeStats(r.Context(), chainFrom, chainTo, startDate, endDate)
@@ -113,8 +161,20 @@ func (s *Server) HandleBridge(w http.ResponseWriter, r *http.Request) {
// HandleTokenDistribution handles GET /api/v1/track3/analytics/token-distribution
func (s *Server) HandleTokenDistribution(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
return
}
if !s.requireDB(w) {
return
}
path := strings.TrimPrefix(r.URL.Path, "/api/v1/track3/analytics/token-distribution/")
contract := strings.ToLower(path)
contract, err := normalizeTrack3RequiredAddress(path)
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", err.Error())
return
}
topN, _ := strconv.Atoi(r.URL.Query().Get("top_n"))
if topN < 1 || topN > 1000 {
@@ -137,8 +197,20 @@ func (s *Server) HandleTokenDistribution(w http.ResponseWriter, r *http.Request)
// HandleAddressRisk handles GET /api/v1/track3/analytics/address-risk/:addr
func (s *Server) HandleAddressRisk(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
return
}
if !s.requireDB(w) {
return
}
path := strings.TrimPrefix(r.URL.Path, "/api/v1/track3/analytics/address-risk/")
address := strings.ToLower(path)
address, err := normalizeTrack3RequiredAddress(path)
if err != nil {
writeError(w, http.StatusBadRequest, "bad_request", err.Error())
return
}
analysis, err := s.riskAnalyzer.AnalyzeAddress(r.Context(), address)
if err != nil {
@@ -165,3 +237,32 @@ func writeError(w http.ResponseWriter, statusCode int, code, message string) {
})
}
func (s *Server) requireDB(w http.ResponseWriter) bool {
if s.db == nil {
writeError(w, http.StatusServiceUnavailable, "service_unavailable", "database not configured")
return false
}
return true
}
func normalizeTrack3OptionalAddress(value string) (string, error) {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return "", nil
}
if !common.IsHexAddress(trimmed) {
return "", fmt.Errorf("invalid address format")
}
return strings.ToLower(common.HexToAddress(trimmed).Hex()), nil
}
func normalizeTrack3RequiredAddress(value string) (string, error) {
normalized, err := normalizeTrack3OptionalAddress(value)
if err != nil {
return "", err
}
if normalized == "" {
return "", fmt.Errorf("address required")
}
return normalized, nil
}