Files
zeekay 826c3d03da refactor: standalone indexerd, drop dead bundles
luxfi/indexer is now a single-purpose repo: the standalone indexerd
chain-indexer daemon. The unified Go binary (indexer + graph + embedded
SPA) lives in luxfi/explorer; the standalone graph daemon lives in
luxfi/graph. One way to do everything.

Changes:
- cmd/indexerd/    new entrypoint (replaces broken cmd/indexer/)
- daemon/          public package: Config, RunIndexers, MountIndexerAPI,
                   MountHealth, ListenAndServe — importable by
                   luxfi/explorer for the unified binary
- Dockerfile       builds only /usr/local/bin/indexerd (port 8091)
- Makefile         single target

Removed:
- cmd/explorer/, cmd/explorerd/  — moved to luxfi/explorer
- cmd/indexer/                   — broken (imports nonexistent
                                    {a,b,k,p,q,t,x,z}chain packages)
- cmd/multichain/, multichain/   — outdated multi-chain manager
- cmd/evmchains/                 — outdated EVM chain registry tool
- cmd/graphd/                    — moved to luxfi/graph
- Dockerfile.{indexer,orig,simple} — pre-canonical leftovers

Indexer config (daemon.Config) is intentionally minimal: data_dir,
http_addr, chains[]. Brand, networks, graph, token, and subgraph
customization is the unified-explorer concern (per-chain in chains.yaml,
read by luxfi/explorer). Extra YAML fields are silently ignored here.
2026-05-05 20:42:41 -07:00

111 lines
3.4 KiB
Go

// Package daemon is the chain-indexer bootstrap: it owns the per-chain
// indexers and the /v1/indexer/* HTTP API. The standalone indexerd binary
// (cmd/indexerd) is the only consumer in this repo. The unified
// luxfi/explorer binary imports it directly to compose indexer + graph +
// frontend into a single Go binary.
package daemon
import (
"fmt"
"os"
"path/filepath"
"regexp"
"gopkg.in/yaml.v3"
)
// ChainConfig defines a single chain to index.
type ChainConfig struct {
Slug string `yaml:"slug"` // URL-safe identifier (e.g., "cchain")
Name string `yaml:"name"` // Display name
ChainID int64 `yaml:"chain_id"` // EVM chain ID (0 for non-EVM)
Type string `yaml:"type"` // evm, dag, linear, solana, bitcoin, cosmos
RPC string `yaml:"rpc"` // RPC endpoint
WS string `yaml:"ws"` // WebSocket endpoint (optional)
CoinSymbol string `yaml:"coin"` // Native coin symbol
Enabled bool `yaml:"enabled"` // Enable indexing
Default bool `yaml:"default"` // Default chain for /v1/indexer/* (no slug prefix)
}
// Config is the top-level chain-indexer configuration. Extra YAML fields
// (frontend brand, graph schemas, etc.) are accepted and ignored — the
// unified luxfi/explorer binary owns those concerns.
type Config struct {
DataDir string `yaml:"data_dir"`
HTTPAddr string `yaml:"http_addr"`
Chains []ChainConfig `yaml:"chains"`
}
// EnabledChains returns only the chains with Enabled=true.
func (c Config) EnabledChains() []ChainConfig {
out := make([]ChainConfig, 0, len(c.Chains))
for _, ch := range c.Chains {
if ch.Enabled {
out = append(out, ch)
}
}
return out
}
// LoadConfig reads, parses and validates a YAML config from disk.
func LoadConfig(path string) (Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return Config{}, err
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return Config{}, err
}
for i := range cfg.Chains {
cfg.Chains[i].RPC = os.Expand(cfg.Chains[i].RPC, os.Getenv)
cfg.Chains[i].WS = os.Expand(cfg.Chains[i].WS, os.Getenv)
if err := validateSlug(cfg.Chains[i].Slug); err != nil {
return Config{}, fmt.Errorf("chain[%d]: %w", i, err)
}
}
return cfg, nil
}
// FindConfig returns the first existing path among a default search list,
// or "" if none are found. Callers may pass an explicit path to skip the search.
func FindConfig(dataDir string) string {
for _, p := range []string{
filepath.Join(dataDir, "chains.yaml"),
"chains.yaml",
"/etc/explorer/chains.yaml",
} {
if _, err := os.Stat(p); err == nil {
return p
}
}
return ""
}
// slugPattern is the set of characters allowed in a chain slug. Slugs feed
// directly into filesystem paths (per-chain SQLite dir) and URL route prefixes
// (/v1/graph/{slug}/…). Restricting them to lowercase-alphanumeric + hyphen
// keeps both surfaces safe from path traversal and URL-encoding surprises.
var slugPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,63}$`)
func validateSlug(slug string) error {
if !slugPattern.MatchString(slug) {
return fmt.Errorf("invalid slug %q: must match ^[a-z0-9][a-z0-9-]{0,63}$", slug)
}
return nil
}
// EnvOr returns the value of an env var, or fallback if unset.
func EnvOr(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
// HomeDir returns the current user's home directory, or "" if unknown.
func HomeDir() string {
h, _ := os.UserHomeDir()
return h
}