neofeeds

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 28, 2026 License: MIT Imports: 31 Imported by: 0

README

NeoFeeds Marble Service

TEE-secured price aggregation + on-chain anchoring service running inside the MarbleRun/EGo enclave mesh.

Responsibilities

  • Poll multiple external HTTP sources (default: Binance, Coinbase, OKX) on a fixed interval (default: 1s).
  • Aggregate values via weighted median.
  • Sign responses with an enclave-held key (NEOFEEDS_SIGNING_KEY).
  • Optionally push updates on-chain to the platform PriceFeed contract (preferred).
  • Enforce publish policy defaults aligned with the platform blueprint:
    • Threshold: 10 bps (0.10%)
    • Hysteresis: 8 bps (0.08%)
    • Min interval: 5s (≤ 1 publish / 5s / symbol)
    • Max rate: 30/min per symbol

Endpoints

  • GET /health, GET /info (provided by the shared BaseService)
  • GET /price/{pair} (canonical: BTC-USD, legacy BTC/USD accepted)
  • GET /prices (latest cached prices from storage, when DB is configured)
  • GET /feeds, GET /sources, GET /config (introspection)

Configuration

NeoFeeds can be configured via:

  1. A YAML/JSON file (ConfigFile), or
  2. A programmatic FeedsConfig (tests/embedding), or
  3. Built-in defaults (DefaultConfig()).
URL & Pair Templating

Each source.url supports placeholders:

  • {base} / {quote}: derived from the feed ID (e.g., BTC-USD)
  • {pair}: constructed using pair_template (recommended for exchanges)

Per-source overrides are supported:

  • base_override: replace base symbol for a single source
  • quote_override: replace quote symbol (e.g., map USD -> USDT)

Example:

update_interval: 1s
publish_policy:
  threshold_bps: 10
  hysteresis_bps: 8
  min_interval: 5s
  max_per_minute: 30

default_sources: [binance, coinbase, okx]

sources:
  - id: binance
    url: "https://api.binance.com/api/v3/ticker/price?symbol={pair}"
    json_path: price
    pair_template: "{base}{quote}"
    quote_override: USDT

  - id: coinbase
    url: "https://api.coinbase.com/v2/prices/{base}-{quote}/spot"
    json_path: data.amount

  - id: okx
    url: "https://www.okx.com/api/v5/market/ticker?instId={pair}"
    json_path: data.0.last
    pair_template: "{base}-{quote}"
    quote_override: USDT

feeds:
  - id: BTC-USD
    enabled: true

On-Chain Anchoring (PriceFeed)

When EnableChainPush is enabled and PriceFeedAddress is configured, NeoFeeds periodically evaluates all enabled feeds and anchors qualifying updates on-chain via PriceFeed.update(...).

Anchoring uses:

  • txproxy for the signed/broadcasted invocation (PriceFeed.update), and
  • an attestation-derived hash included in the contract record.

txproxy holds the TEE signing key (typically backed by globalsigner) and enforces the contract+method allowlist.

The PriceFeed contract enforces monotonic round_id to prevent replay.

The codebase contains an optional Chainlink Arbitrum reader. It is disabled by default to keep default behavior aligned with the platform blueprint (3 HTTP sources + median). To enable it, set ARBITRUM_RPC for the neofeeds marble and pass it via Config.ArbitrumRPC.

Required Secrets

  • NEOFEEDS_SIGNING_KEY: stable signing material for response signatures.

In strict identity / enclave mode, outbound sources must use HTTPS (enforced by configuration validation).

Documentation

Overview

Package neofeeds provides API routes for the price feed aggregation service.

Package neofeeds provides Chainlink price feed integration.

Package neofeeds provides configurable price feed aggregation service.

Package neofeeds provides core logic for the price feed aggregation service.

Package neofeeds provides chain push logic for the price feed aggregation service.

Package neofeeds provides HTTP handlers for the price feed aggregation service.

Package neofeeds provides price feed aggregation service. This service implements a Push/Auto-Update pattern: - TEE periodically fetches prices from multiple sources - TEE aggregates and signs the price data - TEE anchors updates to the platform PriceFeed contract on-chain (optional) - User contracts (or the platform) read prices directly (no callback needed)

Configuration can be loaded from YAML/JSON file for easy customization of data sources and feeds without code changes.

Package neofeeds provides types for the price feed aggregation service.

Index

Constants

View Source
const (
	ServiceID   = "neofeeds"
	ServiceName = "NeoFeeds Service"
	Version     = "3.0.0"

	// Service fee per price update request (in GAS smallest unit)
	ServiceFeePerUpdate = 10000 // 0.0001 GAS
)
View Source
const DefaultArbitrumRPC = "https://arb1.arbitrum.io/rpc"

DefaultArbitrumRPC is the default Arbitrum One RPC endpoint.

Variables

View Source
var ChainlinkFeeds = map[string]*ChainlinkFeedConfig{
	"BTC-USD":  {FeedID: "BTC-USD", Address: "0x6ce185860a4963106506C203335A2910413708e9", Decimals: 8},
	"ETH-USD":  {FeedID: "ETH-USD", Address: "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612", Decimals: 8},
	"LINK-USD": {FeedID: "LINK-USD", Address: "0x86E53CF1B870786351Da77A57575e79CB55812CB", Decimals: 8},
	"SOL-USD":  {FeedID: "SOL-USD", Address: "0x24ceA4b8ce57cdA5058b924B9B9987992450590c", Decimals: 8},
	"BNB-USD":  {FeedID: "BNB-USD", Address: "0x6970460aabF80C5BE983C6b74e5D06dEDCA95D4A", Decimals: 8},
	"DOGE-USD": {FeedID: "DOGE-USD", Address: "0x9A7FB1b3950837a8D9b40517626E11D4127C098C", Decimals: 8},
	"ADA-USD":  {FeedID: "ADA-USD", Address: "0xD9f615A9b820225edbA2d821c4A696a0924051c6", Decimals: 8},
	"AVAX-USD": {FeedID: "AVAX-USD", Address: "0x8bf61728eeDCE2F32c456454d87B5d6eD6150208", Decimals: 8},
	"LTC-USD":  {FeedID: "LTC-USD", Address: "0x5698690a7B7B84F6aa985ef7690A8A7288FBc9c8", Decimals: 8},
	"UNI-USD":  {FeedID: "UNI-USD", Address: "0x9C917083fDb403ab5ADbEC26Ee294f6EcAda2720", Decimals: 8},
	"XRP-USD":  {FeedID: "XRP-USD", Address: "0xB4AD57B52aB9141de9926a3e0C8dc6264c2ef205", Decimals: 8},
}

ChainlinkFeeds defines the Chainlink price feed addresses on Arbitrum One.

Functions

This section is empty.

Types

type ChainlinkClient

type ChainlinkClient struct {
	// contains filtered or unexported fields
}

ChainlinkClient reads prices from Chainlink price feeds on Arbitrum.

func NewChainlinkClient

func NewChainlinkClient(rpcURL string) (*ChainlinkClient, error)

NewChainlinkClient creates a new Chainlink client.

func (*ChainlinkClient) Close

func (c *ChainlinkClient) Close()

Close closes the client connection.

func (*ChainlinkClient) GetPrice

func (c *ChainlinkClient) GetPrice(ctx context.Context, feedID string) (priceFloat float64, decimals int, err error)

GetPrice fetches the latest price from a Chainlink feed.

func (*ChainlinkClient) HasFeed

func (c *ChainlinkClient) HasFeed(feedID string) bool

HasFeed returns true if Chainlink supports this feed.

type ChainlinkFeedConfig

type ChainlinkFeedConfig struct {
	FeedID   string // e.g., "BTC-USD"
	Address  string // Contract address on Arbitrum
	Decimals int    // Price decimals (usually 8)
}

ChainlinkFeedConfig defines a Chainlink price feed configuration.

type Config

type Config struct {
	Marble      *marble.Marble
	DB          database.RepositoryInterface
	ConfigFile  string          // Path to YAML/JSON config file (optional)
	FeedsConfig *NeoFeedsConfig // Direct config (optional, takes precedence over file)
	ArbitrumRPC string          // Arbitrum RPC URL for Chainlink feeds

	// Chain configuration for push pattern
	ChainClient      *chain.Client
	PriceFeedAddress string // Contract address for platform PriceFeed (preferred)
	TxProxy          txproxytypes.Invoker
	UpdateInterval   time.Duration // How often to push prices on-chain (default: from config)
	EnableChainPush  bool          // Enable automatic on-chain price updates

	// GasBank client for service fee deduction (optional)
	GasBank *gasbankclient.Client

	SourceConcurrency int
}

Config holds NeoFeeds service configuration.

type DataType

type DataType string

DataType defines the type of data a feed provides.

const (
	DataTypePrice  DataType = "price"  // Cryptocurrency/forex prices
	DataTypeNumber DataType = "number" // Generic numeric data
	DataTypeString DataType = "string" // Text data
)

type FeedConfig

type FeedConfig struct {
	ID             string        `json:"id" yaml:"id"`                                               // Feed identifier (e.g., "BTC-USD")
	Name           string        `json:"name,omitempty" yaml:"name,omitempty"`                       // Human-readable name
	DataType       DataType      `json:"data_type" yaml:"data_type"`                                 // Type of data
	Pair           string        `json:"pair,omitempty" yaml:"pair,omitempty"`                       // Trading pair for price feeds (e.g., "BTCUSDT")
	Base           string        `json:"base,omitempty" yaml:"base,omitempty"`                       // Base asset (e.g., "BTC")
	Quote          string        `json:"quote,omitempty" yaml:"quote,omitempty"`                     // Quote asset (e.g., "USD")
	Decimals       int           `json:"decimals" yaml:"decimals"`                                   // Decimal precision (default: 8)
	Sources        []string      `json:"sources" yaml:"sources"`                                     // Source IDs to use
	UpdateInterval time.Duration `json:"update_interval,omitempty" yaml:"update_interval,omitempty"` // Per-feed update interval
	Enabled        bool          `json:"enabled" yaml:"enabled"`                                     // Whether feed is active
}

FeedConfig defines a data feed configuration.

type FeedSummary

type FeedSummary struct {
	ID         string `json:"id"`
	Pair       string `json:"pair"` // Canonical symbol (same as ID for price feeds).
	SourcePair string `json:"source_pair,omitempty"`
	Enabled    bool   `json:"enabled"`
	Decimals   int    `json:"decimals"`
}

FeedSummary represents a feed entry returned by GET /feeds.

type FeedsConfig

type FeedsConfig struct {
	Version        string              `json:"version" yaml:"version"`
	Sources        []SourceConfig      `json:"sources" yaml:"sources"`
	Feeds          []FeedConfig        `json:"feeds" yaml:"feeds"`
	DefaultSources []string            `json:"default_sources,omitempty" yaml:"default_sources,omitempty"` // Default sources for feeds that don't specify
	UpdateInterval time.Duration       `json:"update_interval,omitempty" yaml:"update_interval,omitempty"` // Global update interval
	PublishPolicy  PublishPolicyConfig `json:"publish_policy,omitempty" yaml:"publish_policy,omitempty"`
}

FeedsConfig is the root configuration for the neofeeds service.

func DefaultConfig

func DefaultConfig() *FeedsConfig

DefaultConfig returns the default configuration.

By default this is aligned with the MiniApp platform blueprint: - 3 HTTP sources (binance, coinbase, okx) - 1s evaluation interval - 0.10% publish threshold with 0.08% hysteresis - 5s minimum publish interval per symbol

Some feed IDs are also present in the optional Chainlink map (Arbitrum), but Chainlink is disabled unless explicitly configured.

func LoadConfigFromFile

func LoadConfigFromFile(path string) (*FeedsConfig, error)

LoadConfigFromFile loads configuration from a JSON or YAML file.

func (*FeedsConfig) GetEnabledFeeds

func (c *FeedsConfig) GetEnabledFeeds() []FeedConfig

GetEnabledFeeds returns all enabled feeds.

func (*FeedsConfig) GetFeed

func (c *FeedsConfig) GetFeed(id string) *FeedConfig

GetFeed returns a feed by ID.

func (*FeedsConfig) GetSource

func (c *FeedsConfig) GetSource(id string) *SourceConfig

GetSource returns a source by ID.

func (*FeedsConfig) ToJSON

func (c *FeedsConfig) ToJSON() ([]byte, error)

ToJSON serializes config to JSON.

func (*FeedsConfig) ToYAML

func (c *FeedsConfig) ToYAML() ([]byte, error)

ToYAML serializes config to YAML.

func (*FeedsConfig) Validate

func (c *FeedsConfig) Validate() error

Validate validates the configuration and sets defaults.

type NeoFeedsConfig

type NeoFeedsConfig = FeedsConfig

NeoFeedsConfig is kept for backward compatibility.

type PriceResponse

type PriceResponse struct {
	FeedID    string    `json:"feed_id"`
	Pair      string    `json:"pair"`
	Price     int64     `json:"price,string"`
	Decimals  int       `json:"decimals"`
	Timestamp time.Time `json:"timestamp"`
	Sources   []string  `json:"sources"`
	Signature []byte    `json:"signature,omitempty"`
	PublicKey []byte    `json:"public_key,omitempty"`
}

PriceResponse represents a price response. Note: Price uses string serialization to avoid JS Number precision loss for large values.

type PriceSource

type PriceSource struct {
	Name     string `json:"name"`
	URL      string `json:"url"`
	JSONPath string `json:"json_path"`
	Weight   int    `json:"weight"`
}

PriceSource defines a price data source (legacy, use SourceConfig instead).

type PublishPolicyConfig

type PublishPolicyConfig struct {
	// ThresholdBps is the minimum relative change required to consider publishing.
	// Default: 10 bps = 0.10%.
	ThresholdBps int `json:"threshold_bps,omitempty" yaml:"threshold_bps,omitempty"`
	// HysteresisBps is used as a confirmation threshold after a spike is detected.
	// Default: 8 bps = 0.08%.
	HysteresisBps int `json:"hysteresis_bps,omitempty" yaml:"hysteresis_bps,omitempty"`
	// MinInterval is the minimum time between publishes per symbol.
	// Default: 5s (matches the platform blueprint throttle).
	MinInterval time.Duration `json:"min_interval,omitempty" yaml:"min_interval,omitempty"`
	// MaxPerMinute caps publish frequency per symbol (soft cap; enforced in-process).
	// Default: 30.
	MaxPerMinute int `json:"max_per_minute,omitempty" yaml:"max_per_minute,omitempty"`
}

PublishPolicyConfig controls when prices are anchored on-chain. Values are expressed in basis points (bps): 1 bps = 0.01%.

type Service

type Service struct {
	*commonservice.BaseService
	// contains filtered or unexported fields
}

Service implements the NeoFeeds service.

func New

func New(cfg Config) (*Service, error)

New creates a new NeoFeeds service.

func (*Service) GetConfig

func (s *Service) GetConfig() *NeoFeedsConfig

GetConfig returns the current configuration.

func (*Service) GetEnabledFeeds

func (s *Service) GetEnabledFeeds() []FeedConfig

GetEnabledFeeds returns all enabled feeds.

func (*Service) GetPrice

func (s *Service) GetPrice(ctx context.Context, pair string) (*PriceResponse, error)

GetPrice fetches and aggregates price from multiple sources.

Default behavior is to query the configured HTTP sources and aggregate via (weighted) median. If Chainlink is configured, it is treated as an optional additional source (it does not replace HTTP sources).

func (*Service) PushSinglePrice

func (s *Service) PushSinglePrice(ctx context.Context, feedID string) error

PushSinglePrice pushes a single price update on-chain.

type SourceConfig

type SourceConfig struct {
	ID       string            `json:"id" yaml:"id"`
	Name     string            `json:"name" yaml:"name"`
	URL      string            `json:"url" yaml:"url"`             // URL template with {pair}, {base}, {quote} placeholders
	JSONPath string            `json:"json_path" yaml:"json_path"` // JSONPath to extract value
	Weight   int               `json:"weight" yaml:"weight"`       // Weight for aggregation (default: 1)
	Headers  map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"`
	Timeout  time.Duration     `json:"timeout,omitempty" yaml:"timeout,omitempty"` // Request timeout (default: 10s)

	// PairTemplate optionally defines how to construct the {pair} token
	// from the feed base/quote (after applying overrides below).
	// Example: "{base}{quote}" (Binance), "{base}-{quote}" (OKX).
	PairTemplate string `json:"pair_template,omitempty" yaml:"pair_template,omitempty"`
	// BaseOverride and QuoteOverride optionally override the feed base/quote symbols
	// for this particular source (e.g., USD -> USDT on exchanges).
	BaseOverride  string `json:"base_override,omitempty" yaml:"base_override,omitempty"`
	QuoteOverride string `json:"quote_override,omitempty" yaml:"quote_override,omitempty"`
}

SourceConfig defines a data source configuration.

type SourceSummary

type SourceSummary struct {
	ID     string `json:"id"`
	Name   string `json:"name"`
	Weight int    `json:"weight"`
}

SourceSummary represents a configured source returned by GET /sources.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL