// Package node — /api/well-known-contracts endpoint. // // This endpoint lets a freshly-launched client auto-discover the canonical // contract IDs for system services (username registry, governance, …) without // the user having to paste contract IDs into settings by hand. // // Discovery strategy: // // 1. List all deployed contracts via q.GetContracts(). // 2. For each contract, parse its ABI JSON and pull the "contract" name field. // 3. For each distinct name, keep the **earliest-deployed** record — this is // the canonical one. Operators who want to override this (e.g. migrate to // a new registry) will add pinning support via config later; for the MVP // "earliest wins" matches what all nodes see because the chain is ordered. // // The response shape is stable JSON so the client can rely on it: // // { // "count": 3, // "contracts": { // "username_registry": { "contract_id": "…", "name": "username_registry", "version": "1.0.0", "deployed_at": 42 }, // "governance": { "contract_id": "…", "name": "governance", "version": "0.9.0", "deployed_at": 50 }, // … // } // } package node import ( "encoding/json" "fmt" "net/http" ) // WellKnownContract is the per-entry payload returned in /api/well-known-contracts. type WellKnownContract struct { ContractID string `json:"contract_id"` Name string `json:"name"` Version string `json:"version,omitempty"` DeployedAt uint64 `json:"deployed_at"` } // abiHeader is the minimal subset of a contract's ABI JSON we need to look at. type abiHeader struct { Contract string `json:"contract"` Version string `json:"version"` } func registerWellKnownAPI(mux *http.ServeMux, q ExplorerQuery) { mux.HandleFunc("/api/well-known-contracts", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { jsonErr(w, fmt.Errorf("method not allowed"), 405) return } if q.GetContracts == nil { jsonErr(w, fmt.Errorf("contract queries not available on this node"), 503) return } all, err := q.GetContracts() if err != nil { jsonErr(w, err, 500) return } out := map[string]WellKnownContract{} // WASM contracts (stored as ContractRecord in BadgerDB). for _, rec := range all { if rec.ABIJson == "" { continue } var abi abiHeader if err := json.Unmarshal([]byte(rec.ABIJson), &abi); err != nil { continue } if abi.Contract == "" { continue } existing, ok := out[abi.Contract] if !ok || rec.DeployedAt < existing.DeployedAt { out[abi.Contract] = WellKnownContract{ ContractID: rec.ContractID, Name: abi.Contract, Version: abi.Version, DeployedAt: rec.DeployedAt, } } } // Native (in-process Go) contracts. These always win over WASM // equivalents of the same ABI name — the native implementation is // authoritative because every node runs identical Go code, while a // WASM copy might drift (different build, different bytecode). if q.NativeContracts != nil { for _, nc := range q.NativeContracts() { var abi abiHeader if err := json.Unmarshal([]byte(nc.ABIJson), &abi); err != nil { continue } if abi.Contract == "" { continue } out[abi.Contract] = WellKnownContract{ ContractID: nc.ContractID, Name: abi.Contract, Version: abi.Version, DeployedAt: 0, // native contracts exist from block 0 } } } jsonOK(w, map[string]any{ "count": len(out), "contracts": out, }) }) }