// Package node - chain explorer HTTP API and minimal web UI. package node import ( "net/http" "go-blockchain/blockchain" ) // ConnectedPeerRef is an entry returned by /api/peers — one currently-connected // libp2p peer. Mirrors p2p.ConnectedConnectedPeerRef but kept in the node package // so api routes don't pull the p2p package directly. type ConnectedPeerRef struct { ID string `json:"id"` Addrs []string `json:"addrs"` // Version is the peer's last-seen version announce (from gossipsub topic // dchain/version/v1). Empty when the peer hasn't announced yet — either // it's running an older binary without gossip, or it hasn't reached its // first publish tick (up to 60s after connect). Version *PeerVersionRef `json:"version,omitempty"` } // PeerVersionRef mirrors p2p.PeerVersion in a package-local type so // api_routes doesn't import p2p directly. type PeerVersionRef struct { Tag string `json:"tag"` Commit string `json:"commit"` ProtocolVersion int `json:"protocol_version"` Timestamp int64 `json:"timestamp"` ReceivedAt string `json:"received_at,omitempty"` } // NativeContractInfo is the shape ExplorerQuery.NativeContracts returns. // Passed from main.go (which has the blockchain package imported) to the // well-known endpoint below so it can merge native contracts with WASM ones. type NativeContractInfo struct { ContractID string ABIJson string } // ExplorerQuery holds all functions the explorer API needs to read chain state // and accept new transactions. type ExplorerQuery struct { GetBlock func(index uint64) (*blockchain.Block, error) GetTx func(txID string) (*blockchain.TxRecord, error) AddressToPubKey func(addr string) (string, error) Balance func(pubKey string) (uint64, error) Reputation func(pubKey string) (blockchain.RepStats, error) WalletBinding func(pubKey string) (string, error) TxsByAddress func(pubKey string, limit, offset int) ([]*blockchain.TxRecord, error) RecentBlocks func(limit int) ([]*blockchain.Block, error) RecentTxs func(limit int) ([]*blockchain.TxRecord, error) NetStats func() (blockchain.NetStats, error) RegisteredRelays func() ([]blockchain.RegisteredRelayInfo, error) IdentityInfo func(pubKeyOrAddr string) (*blockchain.IdentityInfo, error) ValidatorSet func() ([]string, error) SubmitTx func(tx *blockchain.Transaction) error // ConnectedPeers (optional) returns the local libp2p view of currently // connected peers. Used by /api/peers and /api/network-info so new nodes // can bootstrap from any existing peer's view of the network. May be nil // if the node binary is built without p2p (tests). ConnectedPeers func() []ConnectedPeerRef // ChainID (optional) returns a stable identifier for this chain so a // joiner can sanity-check it's syncing from the right network. ChainID func() string // NativeContracts (optional) returns the list of built-in Go contracts // registered on this node. These appear in /api/well-known-contracts // alongside WASM contracts, so the client doesn't need to distinguish. NativeContracts func() []NativeContractInfo GetContract func(contractID string) (*blockchain.ContractRecord, error) GetContracts func() ([]blockchain.ContractRecord, error) GetContractState func(contractID, key string) ([]byte, error) GetContractLogs func(contractID string, limit int) ([]blockchain.ContractLogEntry, error) Stake func(pubKey string) (uint64, error) GetToken func(tokenID string) (*blockchain.TokenRecord, error) GetTokens func() ([]blockchain.TokenRecord, error) TokenBalance func(tokenID, pubKey string) (uint64, error) GetNFT func(nftID string) (*blockchain.NFTRecord, error) GetNFTs func() ([]blockchain.NFTRecord, error) NFTsByOwner func(ownerPub string) ([]blockchain.NFTRecord, error) // Channel group-messaging lookups (R1). GetChannel returns metadata; // GetChannelMembers returns the Ed25519 pubkey of every current member. // Both may be nil on nodes that don't expose channel state (tests). GetChannel func(channelID string) (*blockchain.CreateChannelPayload, error) GetChannelMembers func(channelID string) ([]string, error) // Events is the SSE hub for the live event stream. Optional — if nil the // /api/events endpoint returns 501 Not Implemented. Events *SSEHub // WS is the websocket hub for low-latency push to mobile/desktop clients. // Optional — if nil the /api/ws endpoint returns 501. WS *WSHub } // ExplorerRouteFlags toggles the optional HTML frontend surfaces. API // endpoints (/api/*) always register — these flags only affect static pages. type ExplorerRouteFlags struct { // DisableUI suppresses the embedded block explorer at `/`, `/address`, // `/tx`, `/node`, `/relays`, `/validators`, `/contract`, `/tokens`, // `/token`, and their `/assets/explorer/*.js|css` dependencies. Useful // for JSON-API-only deployments (headless nodes, mobile-backend nodes). DisableUI bool // DisableSwagger suppresses `/swagger` and `/swagger/openapi.json`. // Useful for hardened private deployments where even API documentation // shouldn't be exposed. Does NOT affect the JSON API itself. DisableSwagger bool } // RegisterExplorerRoutes adds all explorer API, chain API and docs routes to mux. // The variadic flags parameter is optional — passing none (or an empty struct) // registers the full surface (UI + Swagger + JSON API) for backwards compatibility. func RegisterExplorerRoutes(mux *http.ServeMux, q ExplorerQuery, flags ...ExplorerRouteFlags) { var f ExplorerRouteFlags if len(flags) > 0 { f = flags[0] } if !f.DisableUI { registerExplorerPages(mux) } registerExplorerAPI(mux, q) registerChainAPI(mux, q) registerContractAPI(mux, q) registerWellKnownAPI(mux, q) registerWellKnownVersionAPI(mux, q) registerUpdateCheckAPI(mux, q) registerOnboardingAPI(mux, q) registerTokenAPI(mux, q) registerChannelAPI(mux, q) if !f.DisableSwagger { registerSwaggerRoutes(mux) } } // RegisterRelayRoutes adds relay mailbox HTTP endpoints to mux. // Call this after RegisterExplorerRoutes with the relay config. func RegisterRelayRoutes(mux *http.ServeMux, rc RelayConfig) { if rc.Mailbox == nil { return } registerRelayRoutes(mux, rc) } func registerExplorerPages(mux *http.ServeMux) { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } serveExplorerIndex(w, r) }) mux.HandleFunc("/address", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/address" { http.NotFound(w, r) return } serveExplorerAddressPage(w, r) }) mux.HandleFunc("/tx", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/tx" { http.NotFound(w, r) return } serveExplorerTxPage(w, r) }) mux.HandleFunc("/node", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/node" { http.NotFound(w, r) return } serveExplorerNodePage(w, r) }) mux.HandleFunc("/relays", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/relays" { http.NotFound(w, r) return } serveExplorerRelaysPage(w, r) }) mux.HandleFunc("/validators", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/validators" { http.NotFound(w, r) return } serveExplorerValidatorsPage(w, r) }) mux.HandleFunc("/assets/explorer/style.css", serveExplorerCSS) mux.HandleFunc("/assets/explorer/common.js", serveExplorerCommonJS) mux.HandleFunc("/assets/explorer/app.js", serveExplorerJS) mux.HandleFunc("/assets/explorer/address.js", serveExplorerAddressJS) mux.HandleFunc("/assets/explorer/tx.js", serveExplorerTxJS) mux.HandleFunc("/assets/explorer/node.js", serveExplorerNodeJS) mux.HandleFunc("/assets/explorer/relays.js", serveExplorerRelaysJS) mux.HandleFunc("/assets/explorer/validators.js", serveExplorerValidatorsJS) mux.HandleFunc("/contract", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/contract" { http.NotFound(w, r) return } serveExplorerContractPage(w, r) }) mux.HandleFunc("/assets/explorer/contract.js", serveExplorerContractJS) mux.HandleFunc("/tokens", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/tokens" { http.NotFound(w, r) return } serveExplorerTokensPage(w, r) }) mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/token" { http.NotFound(w, r) return } serveExplorerTokenPage(w, r) }) mux.HandleFunc("/assets/explorer/tokens.js", serveExplorerTokensJS) mux.HandleFunc("/assets/explorer/token.js", serveExplorerTokenJS) } func registerExplorerAPI(mux *http.ServeMux, q ExplorerQuery) { mux.HandleFunc("/api/netstats", apiNetStats(q)) mux.HandleFunc("/api/blocks", apiRecentBlocks(q)) mux.HandleFunc("/api/txs/recent", apiRecentTxs(q)) // GET /api/txs/recent?limit=20 mux.HandleFunc("/api/block/", apiBlock(q)) // GET /api/block/{index} mux.HandleFunc("/api/tx/", apiTxByID(q)) // GET /api/tx/{txid} mux.HandleFunc("/api/address/", apiAddress(q)) // GET /api/address/{addr} mux.HandleFunc("/api/node/", apiNode(q)) // GET /api/node/{pubkey|DC...} mux.HandleFunc("/api/relays", apiRelays(q)) // GET /api/relays mux.HandleFunc("/api/identity/", apiIdentity(q)) // GET /api/identity/{pubkey|addr} mux.HandleFunc("/api/validators", apiValidators(q))// GET /api/validators mux.HandleFunc("/api/tx", withWriteTokenGuard(withSubmitTxGuards(apiSubmitTx(q)))) // POST /api/tx (body size + per-IP rate limit + optional token gate) // Live event stream (SSE) — GET /api/events mux.HandleFunc("/api/events", func(w http.ResponseWriter, r *http.Request) { if q.Events == nil { http.Error(w, "event stream not available", http.StatusNotImplemented) return } q.Events.ServeHTTP(w, r) }) // WebSocket gateway — GET /api/ws (upgrades to ws://). Low-latency push // for clients that would otherwise poll balance/inbox/contacts. mux.HandleFunc("/api/ws", func(w http.ResponseWriter, r *http.Request) { if q.WS == nil { http.Error(w, "websocket not available", http.StatusNotImplemented) return } q.WS.ServeHTTP(w, r) }) // Prometheus exposition endpoint — scraped by a Prometheus server. Has // no auth; operators running this in public should put the node behind // a reverse proxy that restricts /metrics to trusted scrapers only. mux.HandleFunc("/metrics", metricsHandler) } func registerTokenAPI(mux *http.ServeMux, q ExplorerQuery) { mux.HandleFunc("/api/tokens", apiTokens(q)) // GET /api/tokens mux.HandleFunc("/api/tokens/", apiTokenByID(q)) // GET /api/tokens/{id} and /api/tokens/{id}/balance/{pubkey} mux.HandleFunc("/api/nfts", apiNFTs(q)) // GET /api/nfts mux.HandleFunc("/api/nfts/", apiNFTByID(q)) // GET /api/nfts/{id} and /api/nfts/owner/{pubkey} } func registerChainAPI(mux *http.ServeMux, q ExplorerQuery) { // Similar to blockchain APIs, but only with features supported by this chain. mux.HandleFunc("/v2/chain/accounts/", apiV2ChainAccountTransactions(q)) // GET /v2/chain/accounts/{account_id}/transactions mux.HandleFunc("/v2/chain/transactions/", apiV2ChainTxByID(q)) // GET /v2/chain/transactions/{tx_id} mux.HandleFunc("/v2/chain/transactions/draft", apiV2ChainDraftTx()) // POST /v2/chain/transactions/draft mux.HandleFunc("/v2/chain/transactions", withWriteTokenGuard(withSubmitTxGuards(apiV2ChainSendTx(q)))) // POST /v2/chain/transactions (body size + rate limit + optional token gate) }