package track1 import ( "encoding/json" "fmt" "math/big" "net/http" "strconv" "strings" "time" "github.com/explorer/backend/libs/go-rpc-gateway" ) // Server handles Track 1 endpoints (uses RPC gateway from lib) type Server struct { rpcGateway *gateway.RPCGateway } // NewServer creates a new Track 1 server func NewServer(rpcGateway *gateway.RPCGateway) *Server { return &Server{ rpcGateway: rpcGateway, } } // HandleLatestBlocks handles GET /api/v1/track1/blocks/latest func (s *Server) HandleLatestBlocks(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") return } limit := 10 if limitStr := r.URL.Query().Get("limit"); limitStr != "" { if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 50 { limit = l } } // Get latest block number blockNumResp, err := s.rpcGateway.GetBlockNumber(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, "rpc_error", err.Error()) return } blockNumHex, ok := blockNumResp.Result.(string) if !ok { writeError(w, http.StatusInternalServerError, "invalid_response", "Invalid block number response") return } // Parse block number blockNum, err := hexToInt(blockNumHex) if err != nil { writeError(w, http.StatusInternalServerError, "parse_error", err.Error()) return } // Fetch blocks blocks := []map[string]interface{}{} for i := 0; i < limit && blockNum-int64(i) >= 0; i++ { blockNumStr := fmt.Sprintf("0x%x", blockNum-int64(i)) blockResp, err := s.rpcGateway.GetBlockByNumber(r.Context(), blockNumStr, false) if err != nil { continue // Skip failed blocks } blockData, ok := blockResp.Result.(map[string]interface{}) if !ok { continue } // Transform to our format block := transformBlock(blockData) blocks = append(blocks, block) } response := map[string]interface{}{ "data": blocks, "pagination": map[string]interface{}{ "page": 1, "limit": limit, }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // HandleLatestTransactions handles GET /api/v1/track1/txs/latest func (s *Server) HandleLatestTransactions(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") return } limit := 10 if limitStr := r.URL.Query().Get("limit"); limitStr != "" { if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 50 { limit = l } } // Get latest block number blockNumResp, err := s.rpcGateway.GetBlockNumber(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, "rpc_error", err.Error()) return } blockNumHex, ok := blockNumResp.Result.(string) if !ok { writeError(w, http.StatusInternalServerError, "invalid_response", "Invalid block number response") return } blockNum, err := hexToInt(blockNumHex) if err != nil { writeError(w, http.StatusInternalServerError, "parse_error", err.Error()) return } // Fetch transactions from recent blocks transactions := []map[string]interface{}{} for i := 0; i < 20 && len(transactions) < limit && blockNum-int64(i) >= 0; i++ { blockNumStr := fmt.Sprintf("0x%x", blockNum-int64(i)) blockResp, err := s.rpcGateway.GetBlockByNumber(r.Context(), blockNumStr, true) if err != nil { continue } blockData, ok := blockResp.Result.(map[string]interface{}) if !ok { continue } txs, ok := blockData["transactions"].([]interface{}) if !ok { continue } for _, tx := range txs { if len(transactions) >= limit { break } txData, ok := tx.(map[string]interface{}) if !ok { continue } transactions = append(transactions, transformTransaction(txData)) } } response := map[string]interface{}{ "data": transactions, "pagination": map[string]interface{}{ "page": 1, "limit": limit, }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // HandleBlockDetail handles GET /api/v1/track1/block/:number func (s *Server) HandleBlockDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") return } path := strings.TrimPrefix(r.URL.Path, "/api/v1/track1/block/") blockNumStr := fmt.Sprintf("0x%x", parseBlockNumber(path)) blockResp, err := s.rpcGateway.GetBlockByNumber(r.Context(), blockNumStr, false) if err != nil { writeError(w, http.StatusNotFound, "not_found", "Block not found") return } blockData, ok := blockResp.Result.(map[string]interface{}) if !ok { writeError(w, http.StatusInternalServerError, "invalid_response", "Invalid block response") return } response := map[string]interface{}{ "data": transformBlock(blockData), } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // HandleTransactionDetail handles GET /api/v1/track1/tx/:hash func (s *Server) HandleTransactionDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") return } path := strings.TrimPrefix(r.URL.Path, "/api/v1/track1/tx/") txHash := path txResp, err := s.rpcGateway.GetTransactionByHash(r.Context(), txHash) if err != nil { writeError(w, http.StatusNotFound, "not_found", "Transaction not found") return } txData, ok := txResp.Result.(map[string]interface{}) if !ok { writeError(w, http.StatusInternalServerError, "invalid_response", "Invalid transaction response") return } response := map[string]interface{}{ "data": transformTransaction(txData), } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // HandleAddressBalance handles GET /api/v1/track1/address/:addr/balance func (s *Server) HandleAddressBalance(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") return } path := strings.TrimPrefix(r.URL.Path, "/api/v1/track1/address/") parts := strings.Split(path, "/") if len(parts) < 2 || parts[1] != "balance" { writeError(w, http.StatusBadRequest, "bad_request", "Invalid path") return } address := parts[0] balanceResp, err := s.rpcGateway.GetBalance(r.Context(), address, "latest") if err != nil { writeError(w, http.StatusInternalServerError, "rpc_error", err.Error()) return } balanceHex, ok := balanceResp.Result.(string) if !ok { writeError(w, http.StatusInternalServerError, "invalid_response", "Invalid balance response") return } balance, err := hexToBigInt(balanceHex) if err != nil { writeError(w, http.StatusInternalServerError, "parse_error", err.Error()) return } response := map[string]interface{}{ "data": map[string]interface{}{ "address": address, "balance": balance.String(), "balance_wei": balance.String(), "balance_ether": weiToEther(balance), }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // HandleBridgeStatus handles GET /api/v1/track1/bridge/status func (s *Server) HandleBridgeStatus(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") return } // Return bridge status (simplified - in production, query bridge contracts) response := map[string]interface{}{ "data": map[string]interface{}{ "status": "operational", "chains": map[string]interface{}{ "138": map[string]interface{}{ "name": "Defi Oracle Meta Mainnet", "status": "operational", "last_sync": time.Now().UTC().Format(time.RFC3339), }, "1": map[string]interface{}{ "name": "Ethereum Mainnet", "status": "operational", "last_sync": time.Now().UTC().Format(time.RFC3339), }, }, "total_transfers_24h": 150, "total_volume_24h": "5000000000000000000000", }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // Helper functions func writeError(w http.ResponseWriter, statusCode int, code, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) json.NewEncoder(w).Encode(map[string]interface{}{ "error": map[string]interface{}{ "code": code, "message": message, }, }) } func hexToInt(hex string) (int64, error) { hex = strings.TrimPrefix(hex, "0x") return strconv.ParseInt(hex, 16, 64) } func parseBlockNumber(s string) int64 { num, err := strconv.ParseInt(s, 10, 64) if err != nil { return 0 } return num } func transformBlock(blockData map[string]interface{}) map[string]interface{} { return map[string]interface{}{ "number": parseHexField(blockData["number"]), "hash": blockData["hash"], "parent_hash": blockData["parentHash"], "timestamp": parseHexTimestamp(blockData["timestamp"]), "transaction_count": countTransactions(blockData["transactions"]), "gas_used": parseHexField(blockData["gasUsed"]), "gas_limit": parseHexField(blockData["gasLimit"]), "miner": blockData["miner"], } } func transformTransaction(txData map[string]interface{}) map[string]interface{} { return map[string]interface{}{ "hash": txData["hash"], "from": txData["from"], "to": txData["to"], "value": txData["value"], "block_number": parseHexField(txData["blockNumber"]), "timestamp": parseHexTimestamp(txData["timestamp"]), } } func parseHexField(field interface{}) interface{} { if str, ok := field.(string); ok { if num, err := hexToInt(str); err == nil { return num } } return field } func parseHexTimestamp(field interface{}) string { if str, ok := field.(string); ok { if num, err := hexToInt(str); err == nil { return time.Unix(num, 0).Format(time.RFC3339) } } return "" } func countTransactions(txs interface{}) int { if txsList, ok := txs.([]interface{}); ok { return len(txsList) } return 0 } func hexToBigInt(hex string) (*big.Int, error) { hex = strings.TrimPrefix(hex, "0x") bigInt := new(big.Int) bigInt, ok := bigInt.SetString(hex, 16) if !ok { return nil, fmt.Errorf("invalid hex number") } return bigInt, nil } func weiToEther(wei *big.Int) string { ether := new(big.Float).Quo(new(big.Float).SetInt(wei), big.NewFloat(1e18)) return ether.Text('f', 18) }