Files
bridge/opnet-watcher/api.go
T
zeekay 118e977946 feat: universal Teleport bridge with 35+ chain plugins
- opnet-watcher: universal binary with plugin architecture
  - 3 functional plugins (OP_NET, Solana, TON)
  - Generic EVMPlugin handles all EVM chains (Ethereum, Base, Arbitrum, etc.)
  - 17 non-EVM chain stubs (Sui, NEAR, Cosmos, XRPL, ICP, etc.)
  - Threshold signing via luxfi/crypto (CGGMP21/FROST)
  - Separate txSigner hot wallet for on-chain submission
  - Multi-tenant API with hostname-based routing
  - Confirmation depth, backing sanity checks, EIP-155 local signing
- manifests: 32 chain configs + 5 tenant configs (lux, zoo, hanzo, pars, spc)
- k8s: fix chain IDs 1330663759→4294967299, ClusterIP services, KMS secrets
- CI: conformance tests gate production deployment
2026-04-06 23:03:19 -07:00

128 lines
3.6 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
// API serves tenant-aware bridge configuration over HTTP.
// All endpoints resolve the tenant from the Host header.
type API struct {
registry *TenantRegistry
startTime time.Time
mux *http.ServeMux
}
// NewAPI creates the HTTP handler.
func NewAPI(registry *TenantRegistry) *API {
a := &API{
registry: registry,
startTime: time.Now(),
mux: http.NewServeMux(),
}
a.mux.HandleFunc("GET /v1/config", a.handleConfig)
a.mux.HandleFunc("GET /v1/chains", a.handleChains)
a.mux.HandleFunc("GET /v1/assets", a.handleAssets)
a.mux.HandleFunc("GET /v1/status", a.handleStatus)
a.mux.HandleFunc("GET /health", a.handleHealth)
return a
}
// ServeHTTP implements http.Handler.
func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.mux.ServeHTTP(w, r)
}
// handleConfig returns the full tenant configuration for the requesting host.
// GET /v1/config
func (a *API) handleConfig(w http.ResponseWriter, r *http.Request) {
t, err := a.registry.TenantForHostname(r.Host)
if err != nil {
writeError(w, http.StatusNotFound, "unknown tenant")
return
}
writeJSON(w, http.StatusOK, t)
}
// handleChains returns the supported chains for the requesting tenant.
// GET /v1/chains
func (a *API) handleChains(w http.ResponseWriter, r *http.Request) {
t, err := a.registry.TenantForHostname(r.Host)
if err != nil {
writeError(w, http.StatusNotFound, "unknown tenant")
return
}
chains, err := a.registry.ChainsForTenant(t.ID)
if err != nil {
writeError(w, http.StatusInternalServerError, "chain resolution failed")
log.Printf("api: chains error tenant=%s: %v", t.ID, err)
return
}
writeJSON(w, http.StatusOK, chains)
}
// handleAssets returns the supported assets for the requesting tenant.
// GET /v1/assets
func (a *API) handleAssets(w http.ResponseWriter, r *http.Request) {
t, err := a.registry.TenantForHostname(r.Host)
if err != nil {
writeError(w, http.StatusNotFound, "unknown tenant")
return
}
assets, err := a.registry.AssetsForTenant(t.ID)
if err != nil {
writeError(w, http.StatusInternalServerError, "asset resolution failed")
log.Printf("api: assets error tenant=%s: %v", t.ID, err)
return
}
writeJSON(w, http.StatusOK, assets)
}
// statusResponse is the shape of GET /v1/status.
type statusResponse struct {
Tenant string `json:"tenant"`
Healthy bool `json:"healthy"`
Uptime string `json:"uptime"`
}
// handleStatus returns bridge status for the requesting tenant.
// GET /v1/status
func (a *API) handleStatus(w http.ResponseWriter, r *http.Request) {
t, err := a.registry.TenantForHostname(r.Host)
if err != nil {
writeError(w, http.StatusNotFound, "unknown tenant")
return
}
resp := statusResponse{
Tenant: t.ID,
Healthy: true,
Uptime: time.Since(a.startTime).Truncate(time.Second).String(),
}
writeJSON(w, http.StatusOK, resp)
}
// handleHealth is the K8s liveness/readiness probe.
// GET /health
func (a *API) handleHealth(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}
// writeJSON encodes v as JSON and writes it to w.
func writeJSON(w http.ResponseWriter, code int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
if err := json.NewEncoder(w).Encode(v); err != nil {
log.Printf("api: json encode error: %v", err)
}
}
// writeError writes a JSON error response.
func writeError(w http.ResponseWriter, code int, msg string) {
writeJSON(w, code, map[string]string{"error": msg})
}