mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
pkg/gateway has carried the whole thing for a while — quote, swap, approval, orders, pools, prices, tokens, stats, history — with a Uniswap provider that speaks the shapes at trade-api.gateway.uniswap.org, and nothing ever started it. `dexd run -http` mounts the D-Chain venue's own handlers under /v1/dex, which is a different surface for a different reader, so an interface asking for a quote reached nothing on any chain. It has an entrypoint now. Two things it did wrong the moment it ran, both the same mistake in different places. The router asked every provider about every chain, so a quote for a Lux market went to Uniswap and came back ACCESS_DENIED — somebody else's API error dressed as a fact about our liquidity. A provider is asked only about the chains it lists now, and one that lists none is taken to serve any, which is the shape a single-chain deployment has. And the venues were attached to the server and never read: `s.venues` was stored by an option nothing consulted, so the chain we settle ourselves was quoted by the chain we do not. Our own venues answer first — an upstream provider that also lists the chain is a second opinion about a market we settle — and a venue holding no liquidity returns nothing rather than an error, so falling through to the providers stays the honest next question. A chain of ours with no provider behind it then says no venue holds the pair, rather than "no providers available", which sends a reader looking for a misconfiguration that is not there. Co-authored-by: Hanzo Dev <[email protected]>
272 lines
6.1 KiB
Go
272 lines
6.1 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sort"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
ErrProviderNotFound = errors.New("provider not found")
|
|
ErrProviderExists = errors.New("provider already exists")
|
|
ErrNoProvidersAvailable = errors.New("no providers available")
|
|
ErrAllProvidersFailed = errors.New("all providers failed")
|
|
)
|
|
|
|
// Registry implements ProviderRegistry with thread-safe provider management
|
|
type Registry struct {
|
|
mu sync.RWMutex
|
|
providers map[string]Provider
|
|
order []string // Sorted by priority
|
|
}
|
|
|
|
// NewRegistry creates a new provider registry
|
|
func NewRegistry() *Registry {
|
|
return &Registry{
|
|
providers: make(map[string]Provider),
|
|
order: make([]string, 0),
|
|
}
|
|
}
|
|
|
|
// RegisterProvider registers a provider
|
|
func (r *Registry) RegisterProvider(provider Provider) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
info := provider.Info()
|
|
if _, exists := r.providers[info.Name]; exists {
|
|
return ErrProviderExists
|
|
}
|
|
|
|
r.providers[info.Name] = provider
|
|
r.order = append(r.order, info.Name)
|
|
r.sortByPriority()
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnregisterProvider removes a provider
|
|
func (r *Registry) UnregisterProvider(name string) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
if _, exists := r.providers[name]; !exists {
|
|
return ErrProviderNotFound
|
|
}
|
|
|
|
delete(r.providers, name)
|
|
|
|
// Remove from order slice
|
|
newOrder := make([]string, 0, len(r.order)-1)
|
|
for _, n := range r.order {
|
|
if n != name {
|
|
newOrder = append(newOrder, n)
|
|
}
|
|
}
|
|
r.order = newOrder
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetProvider returns a provider by name
|
|
func (r *Registry) GetProvider(name string) (Provider, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
provider, exists := r.providers[name]
|
|
if !exists {
|
|
return nil, ErrProviderNotFound
|
|
}
|
|
|
|
return provider, nil
|
|
}
|
|
|
|
// ListProviders returns all registered providers
|
|
func (r *Registry) ListProviders() []ProviderInfo {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
infos := make([]ProviderInfo, 0, len(r.providers))
|
|
for _, name := range r.order {
|
|
if provider, exists := r.providers[name]; exists {
|
|
infos = append(infos, provider.Info())
|
|
}
|
|
}
|
|
|
|
return infos
|
|
}
|
|
|
|
// GetQuoteProviders returns all quote providers sorted by priority
|
|
func (r *Registry) GetQuoteProviders() []QuoteProvider {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
providers := make([]QuoteProvider, 0)
|
|
for _, name := range r.order {
|
|
if provider, exists := r.providers[name]; exists {
|
|
if qp, ok := provider.(QuoteProvider); ok {
|
|
providers = append(providers, qp)
|
|
}
|
|
}
|
|
}
|
|
|
|
return providers
|
|
}
|
|
|
|
// GetQuoteProvidersFor returns the quote providers that serve one chain.
|
|
//
|
|
// Asking every provider about every chain is how a request for a Lux market
|
|
// reached Uniswap and came back ACCESS_DENIED: a provider that does not list
|
|
// the chain cannot answer for it, and its refusal is not a fact about the
|
|
// market. A provider naming no chains at all is taken to serve any — that is
|
|
// the shape a single-chain deployment has.
|
|
func (r *Registry) GetQuoteProvidersFor(chain ChainID) []QuoteProvider {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
providers := make([]QuoteProvider, 0)
|
|
for _, name := range r.order {
|
|
provider, exists := r.providers[name]
|
|
if !exists {
|
|
continue
|
|
}
|
|
qp, ok := provider.(QuoteProvider)
|
|
if !ok {
|
|
continue
|
|
}
|
|
chains := provider.Info().SupportedChains
|
|
if len(chains) == 0 {
|
|
providers = append(providers, qp)
|
|
continue
|
|
}
|
|
for _, c := range chains {
|
|
if c == chain {
|
|
providers = append(providers, qp)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return providers
|
|
}
|
|
|
|
// GetLiquidityProviders returns all liquidity providers
|
|
func (r *Registry) GetLiquidityProviders() []LiquidityProvider {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
providers := make([]LiquidityProvider, 0)
|
|
for _, name := range r.order {
|
|
if provider, exists := r.providers[name]; exists {
|
|
if lp, ok := provider.(LiquidityProvider); ok {
|
|
providers = append(providers, lp)
|
|
}
|
|
}
|
|
}
|
|
|
|
return providers
|
|
}
|
|
|
|
// GetPriceProviders returns all price providers
|
|
func (r *Registry) GetPriceProviders() []PriceProvider {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
providers := make([]PriceProvider, 0)
|
|
for _, name := range r.order {
|
|
if provider, exists := r.providers[name]; exists {
|
|
if pp, ok := provider.(PriceProvider); ok {
|
|
providers = append(providers, pp)
|
|
}
|
|
}
|
|
}
|
|
|
|
return providers
|
|
}
|
|
|
|
// GetConversionProviders returns all conversion providers
|
|
func (r *Registry) GetConversionProviders() []ConversionProvider {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
providers := make([]ConversionProvider, 0)
|
|
for _, name := range r.order {
|
|
if provider, exists := r.providers[name]; exists {
|
|
if cp, ok := provider.(ConversionProvider); ok {
|
|
providers = append(providers, cp)
|
|
}
|
|
}
|
|
}
|
|
|
|
return providers
|
|
}
|
|
|
|
// GetTokenListProviders returns all token list providers
|
|
func (r *Registry) GetTokenListProviders() []TokenListProvider {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
providers := make([]TokenListProvider, 0)
|
|
for _, name := range r.order {
|
|
if provider, exists := r.providers[name]; exists {
|
|
if tp, ok := provider.(TokenListProvider); ok {
|
|
providers = append(providers, tp)
|
|
}
|
|
}
|
|
}
|
|
|
|
return providers
|
|
}
|
|
|
|
// sortByPriority sorts providers by priority (lower = higher priority)
|
|
func (r *Registry) sortByPriority() {
|
|
sort.Slice(r.order, func(i, j int) bool {
|
|
pi := r.providers[r.order[i]].Info().Priority
|
|
pj := r.providers[r.order[j]].Info().Priority
|
|
return pi < pj
|
|
})
|
|
}
|
|
|
|
// HealthCheckAll performs health checks on all providers
|
|
func (r *Registry) HealthCheckAll(ctx context.Context) []HealthCheck {
|
|
r.mu.RLock()
|
|
providers := make([]Provider, 0, len(r.providers))
|
|
for _, name := range r.order {
|
|
providers = append(providers, r.providers[name])
|
|
}
|
|
r.mu.RUnlock()
|
|
|
|
results := make([]HealthCheck, len(providers))
|
|
var wg sync.WaitGroup
|
|
|
|
for i, provider := range providers {
|
|
wg.Add(1)
|
|
go func(idx int, p Provider) {
|
|
defer wg.Done()
|
|
results[idx] = p.HealthCheck(ctx)
|
|
}(i, provider)
|
|
}
|
|
|
|
wg.Wait()
|
|
return results
|
|
}
|
|
|
|
// Close closes all providers
|
|
func (r *Registry) Close() error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
var lastErr error
|
|
for _, provider := range r.providers {
|
|
if err := provider.Close(); err != nil {
|
|
lastErr = err
|
|
}
|
|
}
|
|
|
|
r.providers = make(map[string]Provider)
|
|
r.order = make([]string, 0)
|
|
|
|
return lastErr
|
|
}
|