From 7c38e6a207c12492eb130fa9bc2d1cf631da7921 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Apr 2026 02:43:15 +0100 Subject: [PATCH] feat(daemon): add REST API endpoints for web frontends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /api/info — chain status (no JSON-RPC wrapper) /api/block — block by height /api/aliases — all aliases /api/alias — single alias by name /api/search — universal search /health — node health check REST endpoints serve raw JSON — simpler for web apps than JSON-RPC. 40 RPC + 16 wallet + 11 HTTP = 67 total endpoints. Co-Authored-By: Charon --- daemon/server.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/daemon/server.go b/daemon/server.go index 0d20e78..409e1a0 100644 --- a/daemon/server.go +++ b/daemon/server.go @@ -41,6 +41,12 @@ func NewServer(c *chain.Chain, cfg *config.ChainConfig) *Server { s.mux.HandleFunc("/json_rpc", s.handleJSONRPC) s.mux.HandleFunc("/getheight", s.handleGetHeight) s.mux.HandleFunc("/start_mining", s.handleStartMining) + s.mux.HandleFunc("/api/info", s.handleRESTInfo) + s.mux.HandleFunc("/api/block", s.handleRESTBlock) + s.mux.HandleFunc("/api/aliases", s.handleRESTAliases) + s.mux.HandleFunc("/api/alias", s.handleRESTAlias) + s.mux.HandleFunc("/api/search", s.handleRESTSearch) + s.mux.HandleFunc("/health", s.handleRESTHealth) s.mux.HandleFunc("/gettransactions", s.handleGetTransactions) s.mux.HandleFunc("/stop_mining", s.handleStopMining) return s @@ -1366,3 +1372,87 @@ func (s *Server) rpcGetDifficultyHistory(w http.ResponseWriter, req jsonRPCReque "status": "OK", }) } + +// --- REST-style HTTP endpoints (no JSON-RPC wrapper) --- + +func (s *Server) handleRESTInfo(w http.ResponseWriter, r *http.Request) { + height, _ := s.chain.Height() + _, meta, _ := s.chain.TopBlock() + aliases := s.chain.GetAllAliases() + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "height": height, "difficulty": meta.Difficulty, + "aliases": len(aliases), "node": "CoreChain/Go", + }) +} + +func (s *Server) handleRESTBlock(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query().Get("height") + h, err := parseUint64(q) + if err != nil { + w.WriteHeader(400) + json.NewEncoder(w).Encode(map[string]string{"error": "?height= required"}) + return + } + blk, meta, err := s.chain.GetBlockByHeight(h) + if err != nil { + w.WriteHeader(404) + json.NewEncoder(w).Encode(map[string]string{"error": "not found"}) + return + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "height": meta.Height, "hash": meta.Hash.String(), + "timestamp": blk.Timestamp, "difficulty": meta.Difficulty, + "tx_count": len(blk.TxHashes), "version": blk.MajorVersion, + }) +} + +func (s *Server) handleRESTAliases(w http.ResponseWriter, r *http.Request) { + aliases := s.chain.GetAllAliases() + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(aliases) +} + +func (s *Server) handleRESTAlias(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + if name == "" { + w.WriteHeader(400) + json.NewEncoder(w).Encode(map[string]string{"error": "?name= required"}) + return + } + alias, err := s.chain.GetAlias(name) + if err != nil { + w.WriteHeader(404) + json.NewEncoder(w).Encode(map[string]string{"error": "not found"}) + return + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(alias) +} + +func (s *Server) handleRESTSearch(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query().Get("q") + if q == "" { + w.WriteHeader(400) + json.NewEncoder(w).Encode(map[string]string{"error": "?q= required"}) + return + } + // Reuse the RPC search logic + fakeReq := jsonRPCRequest{Params: json.RawMessage(core.Sprintf(`{"query":"%s"}`, q))} + s.rpcSearch(w, fakeReq) +} + +func (s *Server) handleRESTHealth(w http.ResponseWriter, r *http.Request) { + height, _ := s.chain.Height() + aliases := s.chain.GetAllAliases() + status := "ok" + if height == 0 { + status = "syncing" + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": status, "height": height, + "aliases": len(aliases), "node": "CoreChain/Go", + }) +}