package rest import ( "context" "encoding/json" "fmt" "net/http" "strconv" "time" ) // handleEtherscanAPI handles GET /api?module=...&action=... // This provides Etherscan-compatible API endpoints func (s *Server) handleEtherscanAPI(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w) return } if !s.requireDB(w) { return } module := r.URL.Query().Get("module") action := r.URL.Query().Get("action") // Etherscan-compatible response structure type EtherscanResponse struct { Status string `json:"status"` Message string `json:"message"` Result interface{} `json:"result"` } // Validate required parameters if module == "" || action == "" { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) response := EtherscanResponse{ Status: "0", Message: "Params 'module' and 'action' are required parameters", Result: nil, } json.NewEncoder(w).Encode(response) return } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() var response EtherscanResponse switch module { case "block": switch action { case "eth_block_number": // Get latest block number var blockNumber int64 err := s.db.QueryRow(ctx, `SELECT MAX(number) FROM blocks WHERE chain_id = $1`, s.chainID, ).Scan(&blockNumber) if err != nil { response = EtherscanResponse{ Status: "0", Message: "Error", Result: "0x0", } } else { response = EtherscanResponse{ Status: "1", Message: "OK", Result: fmt.Sprintf("0x%x", blockNumber), } } case "eth_get_block_by_number": tag := r.URL.Query().Get("tag") boolean := r.URL.Query().Get("boolean") == "true" // Parse block number from tag (can be "latest", "0x...", or decimal) var blockNumber int64 if tag == "latest" { err := s.db.QueryRow(ctx, `SELECT MAX(number) FROM blocks WHERE chain_id = $1`, s.chainID, ).Scan(&blockNumber) if err != nil { response = EtherscanResponse{ Status: "0", Message: "Error", Result: nil, } break } } else if len(tag) > 2 && tag[:2] == "0x" { // Hex format parsed, err := strconv.ParseInt(tag[2:], 16, 64) if err != nil { response = EtherscanResponse{ Status: "0", Message: "Invalid block number", Result: nil, } break } blockNumber = parsed } else { // Decimal format parsed, err := strconv.ParseInt(tag, 10, 64) if err != nil { response = EtherscanResponse{ Status: "0", Message: "Invalid block number", Result: nil, } break } blockNumber = parsed } // Get block data var hash, parentHash, miner string var timestamp time.Time var transactionCount int var gasUsed, gasLimit int64 var transactions []string query := ` SELECT hash, parent_hash, timestamp, miner, transaction_count, gas_used, gas_limit FROM blocks WHERE chain_id = $1 AND number = $2 ` err := s.db.QueryRow(ctx, query, s.chainID, blockNumber).Scan( &hash, &parentHash, ×tamp, &miner, &transactionCount, &gasUsed, &gasLimit, ) if err != nil { response = EtherscanResponse{ Status: "0", Message: "Block not found", Result: nil, } break } // If boolean is true, get full transaction objects if boolean { txQuery := ` SELECT hash FROM transactions WHERE chain_id = $1 AND block_number = $2 ORDER BY transaction_index ` rows, err := s.db.Query(ctx, txQuery, s.chainID, blockNumber) if err == nil { defer rows.Close() for rows.Next() { var txHash string if err := rows.Scan(&txHash); err == nil { transactions = append(transactions, txHash) } } } } else { // Just get transaction hashes txQuery := ` SELECT hash FROM transactions WHERE chain_id = $1 AND block_number = $2 ORDER BY transaction_index ` rows, err := s.db.Query(ctx, txQuery, s.chainID, blockNumber) if err == nil { defer rows.Close() for rows.Next() { var txHash string if err := rows.Scan(&txHash); err == nil { transactions = append(transactions, txHash) } } } } blockResult := map[string]interface{}{ "number": fmt.Sprintf("0x%x", blockNumber), "hash": hash, "parentHash": parentHash, "timestamp": fmt.Sprintf("0x%x", timestamp.Unix()), "miner": miner, "transactions": transactions, "transactionCount": fmt.Sprintf("0x%x", transactionCount), "gasUsed": fmt.Sprintf("0x%x", gasUsed), "gasLimit": fmt.Sprintf("0x%x", gasLimit), } response = EtherscanResponse{ Status: "1", Message: "OK", Result: blockResult, } default: response = EtherscanResponse{ Status: "0", Message: "Invalid action", Result: nil, } } default: response = EtherscanResponse{ Status: "0", Message: "Invalid module", Result: nil, } } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) }