mirror of
https://github.com/luxfi/maker.git
synced 2026-10-30 16:20:04 +00:00
Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers; omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
193 lines
7.4 KiB
Go
193 lines
7.4 KiB
Go
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package maker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"maps"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// coherence_config.go parses the 12-factor market spec and builds the composite
|
|
// reference oracle. The spec is the single source of truth for WHICH markets the
|
|
// service pegs and WHAT each tracks. Three orthogonal reference SOURCE forms, one
|
|
// per pricing reality:
|
|
// - a Kraken query pair -> the live CEX mid (a USD-quoted major)
|
|
// - "fixed:<price>" -> a configured constant (a non-Kraken asset)
|
|
// - "ratio:<pair>/<denom>" -> Kraken(pair) / denom (a LUX-quoted major)
|
|
//
|
|
// The ratio form is how a base is quoted in a numeraire the CEX does not list: LETH
|
|
// priced in LUX is (ETH/USD)_kraken / (LUX/USD)_denom. It composes the live CEX
|
|
// oracle with the LUX/USD reference into the base/LUX mid, so an L*/LUX book pegs to
|
|
// the real cross-rate — never a fabricated price, never a second oracle to reconcile.
|
|
//
|
|
// COHERENCE_MARKETS = "SYMBOL=SOURCE:BASE:QUOTE[:SIZE],..."
|
|
//
|
|
// SYMBOL the reference symbol, e.g. ETH/USD or LETH/LUX
|
|
// SOURCE a Kraken query pair (ETHUSD, XBTUSD), "fixed:<price>", or
|
|
// "ratio:<krakenPair>/<denomUSD>"
|
|
// BASE the base (priced) ERC-20 address (currency candidate)
|
|
// QUOTE the quote (numeraire) ERC-20 address
|
|
// SIZE optional resting size per side in base units (default 100000)
|
|
//
|
|
// Examples:
|
|
//
|
|
// ETH/USD=ETHUSD:0xEth..:0xUsd..
|
|
// LUX/USD=fixed:12.5:0xLux..:0xUsd..
|
|
// LETH/LUX=ratio:ETHUSD/12.5:0xLeth..:0xWlux..
|
|
|
|
// RatioRef is a cross-rate reference: mid(symbol) = Kraken(Pair) / Denom. Pair is
|
|
// the CEX-listed leg (e.g. ETHUSD), Denom the numeraire's USD price (e.g. LUX/USD).
|
|
type RatioRef struct {
|
|
Pair string
|
|
Denom float64
|
|
}
|
|
|
|
// ParseCoherenceMarkets parses the market spec into resolved markets plus the three
|
|
// reference maps that build the oracle: Kraken pairs, fixed mids, and ratio refs.
|
|
func ParseCoherenceMarkets(spec string) ([]CoherenceMarket, map[string]string, map[string]float64, map[string]RatioRef, error) {
|
|
spec = strings.TrimSpace(spec)
|
|
if spec == "" {
|
|
return nil, nil, nil, nil, fmt.Errorf("empty COHERENCE_MARKETS")
|
|
}
|
|
var markets []CoherenceMarket
|
|
krakenPairs := map[string]string{}
|
|
fixed := map[string]float64{}
|
|
ratio := map[string]RatioRef{}
|
|
|
|
for entry := range strings.SplitSeq(spec, ",") {
|
|
entry = strings.TrimSpace(entry)
|
|
if entry == "" {
|
|
continue
|
|
}
|
|
sym, rest, ok := strings.Cut(entry, "=")
|
|
if !ok {
|
|
return nil, nil, nil, nil, fmt.Errorf("market %q: want SYMBOL=SOURCE:BASE:QUOTE", entry)
|
|
}
|
|
sym = strings.TrimSpace(sym)
|
|
fields := strings.Split(strings.TrimSpace(rest), ":")
|
|
|
|
var krakenPair string
|
|
var fixedPrice float64
|
|
var ratioRef RatioRef
|
|
switch {
|
|
case len(fields) >= 2 && strings.EqualFold(fields[0], "fixed"):
|
|
// "fixed:<price>" consumes two colon fields (source + value).
|
|
p, err := strconv.ParseFloat(fields[1], 64)
|
|
if err != nil || p <= 0 {
|
|
return nil, nil, nil, nil, fmt.Errorf("market %s: bad fixed price %q", sym, fields[1])
|
|
}
|
|
fixedPrice = p
|
|
fields = fields[2:]
|
|
case len(fields) >= 2 && strings.EqualFold(fields[0], "ratio"):
|
|
// "ratio:<pair>/<denom>" consumes two colon fields (source + "pair/denom").
|
|
pair, denomStr, ok := strings.Cut(fields[1], "/")
|
|
if !ok || strings.TrimSpace(pair) == "" {
|
|
return nil, nil, nil, nil, fmt.Errorf("market %s: ratio wants <krakenPair>/<denom>, got %q", sym, fields[1])
|
|
}
|
|
denom, err := strconv.ParseFloat(strings.TrimSpace(denomStr), 64)
|
|
if err != nil || denom <= 0 {
|
|
return nil, nil, nil, nil, fmt.Errorf("market %s: bad ratio denom %q", sym, denomStr)
|
|
}
|
|
ratioRef = RatioRef{Pair: strings.TrimSpace(pair), Denom: denom}
|
|
fields = fields[2:]
|
|
default:
|
|
krakenPair = fields[0]
|
|
fields = fields[1:]
|
|
}
|
|
if len(fields) < 2 {
|
|
return nil, nil, nil, nil, fmt.Errorf("market %s: want SOURCE:BASE:QUOTE[:SIZE]", sym)
|
|
}
|
|
base := common.HexToAddress(strings.TrimSpace(fields[0]))
|
|
quote := common.HexToAddress(strings.TrimSpace(fields[1]))
|
|
if base == (common.Address{}) || quote == (common.Address{}) {
|
|
return nil, nil, nil, nil, fmt.Errorf("market %s: base/quote must be non-zero ERC-20 addresses", sym)
|
|
}
|
|
var size uint64
|
|
if len(fields) >= 3 {
|
|
n, err := strconv.ParseUint(strings.TrimSpace(fields[2]), 10, 64)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, fmt.Errorf("market %s: bad size %q", sym, fields[2])
|
|
}
|
|
size = n
|
|
}
|
|
markets = append(markets, CoherenceMarket{
|
|
Symbol: sym, KrakenPair: krakenPair, Base: base, Quote: quote, Size: size,
|
|
})
|
|
switch {
|
|
case krakenPair != "":
|
|
krakenPairs[sym] = krakenPair
|
|
case ratioRef.Pair != "":
|
|
ratio[sym] = ratioRef
|
|
default:
|
|
fixed[sym] = fixedPrice
|
|
}
|
|
}
|
|
if len(markets) == 0 {
|
|
return nil, nil, nil, nil, fmt.Errorf("no markets parsed from %q", spec)
|
|
}
|
|
return markets, krakenPairs, fixed, ratio, nil
|
|
}
|
|
|
|
// compositeOracle routes each symbol to its reference: a live Kraken mid for a
|
|
// CEX-listed major, a configured fixed mid for a non-Kraken asset, or a cross-rate
|
|
// (Kraken(pair)/denom) for a base quoted in a non-USD numeraire (LETH/LUX). It
|
|
// satisfies Oracle (GetMidPrice). Composed, not braided — each source prices the
|
|
// symbols it knows; an unknown symbol errors (never a fabricated price). The Kraken
|
|
// feed serves BOTH the direct majors and the ratio legs from ONE feed: a ratio
|
|
// symbol is registered in the feed under its own symbol keyed to the underlying
|
|
// pair, then divided by the denom here.
|
|
type compositeOracle struct {
|
|
kraken MidProvider // live Kraken feed (nil if no Kraken/ratio markets)
|
|
fixed map[string]float64 // symbol -> fixed reference mid
|
|
ratio map[string]RatioRef // symbol -> (kraken leg, USD denom); mid = Kraken(leg)/denom
|
|
}
|
|
|
|
// NewCoherenceOracle builds the composite reference from the parsed maps. The
|
|
// Kraken feed reuses provider_kraken.go (the live-proven public-data oracle) for
|
|
// both the direct majors and every ratio leg, so there is exactly ONE live feed.
|
|
func NewCoherenceOracle(krakenPairs map[string]string, fixed map[string]float64, ratio map[string]RatioRef, logger *slog.Logger) Oracle {
|
|
// One symbol->pair map for the shared feed: direct majors keyed by their own
|
|
// symbol, ratio symbols keyed to their underlying Kraken leg.
|
|
feedPairs := map[string]string{}
|
|
maps.Copy(feedPairs, krakenPairs)
|
|
for sym, r := range ratio {
|
|
feedPairs[sym] = r.Pair
|
|
}
|
|
var kf MidProvider
|
|
if len(feedPairs) > 0 {
|
|
kf = NewKrakenFeed(feedPairs, "kraken", logger)
|
|
}
|
|
return &compositeOracle{kraken: kf, fixed: fixed, ratio: ratio}
|
|
}
|
|
|
|
// GetMidPrice returns the live reference for a symbol: a ratio cross-rate first
|
|
// (its Kraken leg divided by the denom), then a direct Kraken mid, then a fixed mid.
|
|
func (o *compositeOracle) GetMidPrice(ctx context.Context, symbol string) (float64, error) {
|
|
if r, ok := o.ratio[symbol]; ok {
|
|
if o.kraken == nil {
|
|
return 0, fmt.Errorf("no kraken feed for ratio %s", symbol)
|
|
}
|
|
leg, err := o.kraken.GetMidPrice(ctx, symbol)
|
|
if err != nil || leg <= 0 {
|
|
return 0, fmt.Errorf("ratio leg %s (%s): %v", symbol, r.Pair, err)
|
|
}
|
|
return leg / r.Denom, nil
|
|
}
|
|
if o.kraken != nil {
|
|
if mid, err := o.kraken.GetMidPrice(ctx, symbol); err == nil && mid > 0 {
|
|
return mid, nil
|
|
}
|
|
}
|
|
if p, ok := o.fixed[symbol]; ok && p > 0 {
|
|
return p, nil
|
|
}
|
|
return 0, fmt.Errorf("no reference for %s", symbol)
|
|
}
|