Unify agent routing and tighten exchange config

This commit is contained in:
lky-spec
2026-04-28 11:58:58 +08:00
parent d481b3d88c
commit 30a703a827
12 changed files with 679 additions and 77 deletions

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"nofx/crypto"
"nofx/logger"
"strings"
"time"
"github.com/google/uuid"
@@ -57,6 +58,9 @@ func (s *ExchangeStore) initTables() error {
// Still run data migrations
s.migrateToMultiAccount()
s.db.Model(&Exchange{}).Where("account_name = '' OR account_name IS NULL").Update("account_name", "Default")
if err := s.cleanupIncompleteExchangeConfigs(); err != nil {
logger.Warnf("Exchange cleanup migration warning: %v", err)
}
return nil
}
}
@@ -72,10 +76,48 @@ func (s *ExchangeStore) initTables() error {
// Fix empty account_name for existing records
s.db.Model(&Exchange{}).Where("account_name = '' OR account_name IS NULL").Update("account_name", "Default")
if err := s.cleanupIncompleteExchangeConfigs(); err != nil {
logger.Warnf("Exchange cleanup migration warning: %v", err)
}
return nil
}
func (s *ExchangeStore) cleanupIncompleteExchangeConfigs() error {
var exchanges []Exchange
if err := s.db.Find(&exchanges).Error; err != nil {
return err
}
for _, exchange := range exchanges {
missing := MissingRequiredExchangeCredentialFields(
exchange.ExchangeType,
string(exchange.APIKey),
string(exchange.SecretKey),
string(exchange.Passphrase),
exchange.HyperliquidWalletAddr,
exchange.AsterUser,
exchange.AsterSigner,
string(exchange.AsterPrivateKey),
exchange.LighterWalletAddr,
string(exchange.LighterAPIKeyPrivateKey),
)
if len(missing) > 0 {
if err := s.db.Delete(&Exchange{}, "id = ? AND user_id = ?", exchange.ID, exchange.UserID).Error; err != nil {
return err
}
logger.Infof("🧹 Removed incomplete exchange config during migration: id=%s user=%s missing=%s", exchange.ID, exchange.UserID, strings.Join(missing, ","))
continue
}
if !exchange.Enabled {
if err := s.db.Model(&Exchange{}).Where("id = ? AND user_id = ?", exchange.ID, exchange.UserID).Update("enabled", true).Error; err != nil {
return err
}
logger.Infof("🧹 Enabled complete exchange config during migration: id=%s user=%s", exchange.ID, exchange.UserID)
}
}
return nil
}
// migrateToMultiAccount migrates old schema (id=exchange_type) to new schema (id=UUID)
func (s *ExchangeStore) migrateToMultiAccount() error {
// Check if migration is needed by looking for old-style IDs (non-UUID)
@@ -188,6 +230,10 @@ func (s *ExchangeStore) Create(userID, exchangeType, accountName string, enabled
asterUser, asterSigner, asterPrivateKey,
lighterWalletAddr, lighterPrivateKey, lighterApiKeyPrivateKey string, lighterApiKeyIndex int) (string, error) {
if missing := MissingRequiredExchangeCredentialFields(exchangeType, apiKey, secretKey, passphrase, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey, lighterWalletAddr, lighterApiKeyPrivateKey); len(missing) > 0 {
return "", fmt.Errorf("missing required exchange fields: %s", strings.Join(missing, ", "))
}
id := uuid.New().String()
name, typ := getExchangeNameAndType(exchangeType)
@@ -205,7 +251,7 @@ func (s *ExchangeStore) Create(userID, exchangeType, accountName string, enabled
UserID: userID,
Name: name,
Type: typ,
Enabled: enabled,
Enabled: true,
APIKey: crypto.EncryptedString(apiKey),
SecretKey: crypto.EncryptedString(secretKey),
Passphrase: crypto.EncryptedString(passphrase),
@@ -232,10 +278,10 @@ func (s *ExchangeStore) Update(userID, id string, enabled bool, apiKey, secretKe
hyperliquidWalletAddr string, hyperliquidUnifiedAcct bool,
asterUser, asterSigner, asterPrivateKey, lighterWalletAddr, lighterPrivateKey, lighterApiKeyPrivateKey string, lighterApiKeyIndex int) error {
logger.Debugf("🔧 ExchangeStore.Update: userID=%s, id=%s, enabled=%v", userID, id, enabled)
logger.Debugf("🔧 ExchangeStore.Update: userID=%s, id=%s", userID, id)
updates := map[string]interface{}{
"enabled": enabled,
"enabled": true,
"testnet": testnet,
"hyperliquid_wallet_addr": hyperliquidWalletAddr,
"hyperliquid_unified_account": hyperliquidUnifiedAcct,

View File

@@ -2,6 +2,55 @@ package store
import "strings"
func MissingRequiredExchangeCredentialFields(exchangeType, apiKey, secretKey, passphrase, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey, lighterWalletAddr, lighterAPIKeyPrivateKey string) []string {
switch strings.ToLower(strings.TrimSpace(exchangeType)) {
case "binance", "bybit", "gate", "indodax":
return missingNamedFields(
namedField{"api_key", apiKey},
namedField{"secret_key", secretKey},
)
case "okx", "bitget", "kucoin":
return missingNamedFields(
namedField{"api_key", apiKey},
namedField{"secret_key", secretKey},
namedField{"passphrase", passphrase},
)
case "hyperliquid":
return missingNamedFields(
namedField{"api_key", apiKey},
namedField{"hyperliquid_wallet_addr", hyperliquidWalletAddr},
)
case "aster":
return missingNamedFields(
namedField{"aster_user", asterUser},
namedField{"aster_signer", asterSigner},
namedField{"aster_private_key", asterPrivateKey},
)
case "lighter":
return missingNamedFields(
namedField{"lighter_wallet_addr", lighterWalletAddr},
namedField{"lighter_api_key_private_key", lighterAPIKeyPrivateKey},
)
default:
return []string{"exchange_type"}
}
}
type namedField struct {
name string
value string
}
func missingNamedFields(fields ...namedField) []string {
missing := make([]string, 0, len(fields))
for _, field := range fields {
if strings.TrimSpace(field.value) == "" {
missing = append(missing, field.name)
}
}
return missing
}
func IsVisibleAIModel(model *AIModel) bool {
if model == nil {
return false
@@ -45,4 +94,3 @@ func IsVisibleStrategy(strategy *Strategy) bool {
}
return strings.TrimSpace(strategy.Name) != ""
}