Files
zeekayandHanzo Dev 01f5bc490e The trading surface is served, and each chain is asked of whoever holds it
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]>
2026-09-08 22:57:42 -07:00

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
}