feat: implement hybrid database architecture and frontend encryption

- Add PostgreSQL + SQLite hybrid database support with automatic switching
- Implement frontend AES-GCM + RSA-OAEP encryption for sensitive data
- Add comprehensive DatabaseInterface with all required methods
- Fix compilation issues with interface consistency
- Update all database method signatures to use DatabaseInterface
- Add missing UpdateTraderInitialBalance method to PostgreSQL implementation
- Integrate RSA public key distribution via /api/config endpoint
- Add frontend crypto service with proper error handling
- Support graceful degradation between encrypted and plaintext transmission
- Add directory creation for RSA keys and PEM parsing fixes
- Test both SQLite and PostgreSQL modes successfully

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
icy
2025-11-06 01:50:06 +08:00
parent aecc5e58a1
commit 65053518d6
104 changed files with 16864 additions and 4152 deletions

View File

@@ -2,10 +2,12 @@ package trader
import (
"context"
"crypto/ecdsa"
"encoding/json"
"fmt"
"log"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/crypto"
"github.com/sonirico/go-hyperliquid"
@@ -22,6 +24,9 @@ type HyperliquidTrader struct {
// NewHyperliquidTrader 创建Hyperliquid交易器
func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool) (*HyperliquidTrader, error) {
// 去掉私钥的 0x 前缀(如果有,不区分大小写)
privateKeyHex = strings.TrimPrefix(strings.ToLower(privateKeyHex), "0x")
// 解析私钥
privateKey, err := crypto.HexToECDSA(privateKeyHex)
if err != nil {
@@ -34,13 +39,18 @@ func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool)
apiURL = hyperliquid.TestnetAPIURL
}
// // 从私钥生成钱包地址
// pubKey := privateKey.Public()
// publicKeyECDSA, ok := pubKey.(*ecdsa.PublicKey)
// if !ok {
// return nil, fmt.Errorf("无法转换公钥")
// }
// walletAddr := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
// 从私钥生成钱包地址(如果未提供)
if walletAddr == "" {
pubKey := privateKey.Public()
publicKeyECDSA, ok := pubKey.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("无法转换公钥")
}
walletAddr = crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
log.Printf("✓ 从私钥自动生成钱包地址: %s", walletAddr)
} else {
log.Printf("✓ 使用提供的钱包地址: %s", walletAddr)
}
ctx := context.Background()
@@ -76,23 +86,54 @@ func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool)
func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) {
log.Printf("🔄 正在调用Hyperliquid API获取账户余额...")
// 获取账户状态
// ✅ Step 1: 查询 Spot 现货账户余额
spotState, err := t.exchange.Info().SpotUserState(t.ctx, t.walletAddr)
var spotUSDCBalance float64 = 0.0
if err != nil {
log.Printf("⚠️ 查询 Spot 余额失败(可能无现货资产): %v", err)
} else if spotState != nil && len(spotState.Balances) > 0 {
for _, balance := range spotState.Balances {
if balance.Coin == "USDC" {
spotUSDCBalance, _ = strconv.ParseFloat(balance.Total, 64)
log.Printf("✓ 发现 Spot 现货余额: %.2f USDC", spotUSDCBalance)
break
}
}
}
// ✅ Step 2: 查询 Perpetuals 合约账户状态
accountState, err := t.exchange.Info().UserState(t.ctx, t.walletAddr)
if err != nil {
log.Printf("❌ Hyperliquid API调用失败: %v", err)
log.Printf("❌ Hyperliquid Perpetuals API调用失败: %v", err)
return nil, fmt.Errorf("获取账户信息失败: %w", err)
}
// 解析余额信息MarginSummary字段都是string
result := make(map[string]interface{})
// 🔍 调试打印API返回的完整CrossMarginSummary结构
summaryJSON, _ := json.MarshalIndent(accountState.MarginSummary, " ", " ")
log.Printf("🔍 [DEBUG] Hyperliquid API CrossMarginSummary完整数据:")
log.Printf("%s", string(summaryJSON))
// ✅ Step 3: 根据保证金模式动态选择正确的摘要(CrossMarginSummary 或 MarginSummary
var accountValue, totalMarginUsed float64
var summaryType string
var summary interface{}
accountValue, _ := strconv.ParseFloat(accountState.MarginSummary.AccountValue, 64)
totalMarginUsed, _ := strconv.ParseFloat(accountState.MarginSummary.TotalMarginUsed, 64)
if t.isCrossMargin {
// 全仓模式:使用 CrossMarginSummary
accountValue, _ = strconv.ParseFloat(accountState.CrossMarginSummary.AccountValue, 64)
totalMarginUsed, _ = strconv.ParseFloat(accountState.CrossMarginSummary.TotalMarginUsed, 64)
summaryType = "CrossMarginSummary (全仓)"
summary = accountState.CrossMarginSummary
} else {
// 逐仓模式:使用 MarginSummary
accountValue, _ = strconv.ParseFloat(accountState.MarginSummary.AccountValue, 64)
totalMarginUsed, _ = strconv.ParseFloat(accountState.MarginSummary.TotalMarginUsed, 64)
summaryType = "MarginSummary (逐仓)"
summary = accountState.MarginSummary
}
// 🔍 调试打印API返回的完整摘要结构
summaryJSON, _ := json.MarshalIndent(summary, " ", " ")
log.Printf("🔍 [DEBUG] Hyperliquid API %s 完整数据:", summaryType)
log.Printf("%s", string(summaryJSON))
// ⚠️ 关键修复:从所有持仓中累加真正的未实现盈亏
totalUnrealizedPnl := 0.0
@@ -109,16 +150,47 @@ func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) {
// 需要返回"不包含未实现盈亏的钱包余额"
walletBalanceWithoutUnrealized := accountValue - totalUnrealizedPnl
result["totalWalletBalance"] = walletBalanceWithoutUnrealized // 钱包余额(不含未实现盈亏
result["availableBalance"] = accountValue - totalMarginUsed // 可用余额(总净值 - 占用保证金)
result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未实现盈亏
// ✅ Step 4: 使用 Withdrawable 欄位PR #443
// Withdrawable 是官方提供的真实可提现余额,比简单计算更可靠
availableBalance := 0.0
if accountState.Withdrawable != "" {
withdrawable, err := strconv.ParseFloat(accountState.Withdrawable, 64)
if err == nil && withdrawable > 0 {
availableBalance = withdrawable
log.Printf("✓ 使用 Withdrawable 作为可用余额: %.2f", availableBalance)
}
}
log.Printf("✓ Hyperliquid 账户: 总净值=%.2f (钱包%.2f+未实现%.2f), 可用=%.2f, 保证金占用=%.2f",
// 降级方案:如果没有 Withdrawable使用简单计算
if availableBalance == 0 && accountState.Withdrawable == "" {
availableBalance = accountValue - totalMarginUsed
if availableBalance < 0 {
log.Printf("⚠️ 计算出的可用余额为负数 (%.2f),重置为 0", availableBalance)
availableBalance = 0
}
}
// ✅ Step 5: 正確處理 Spot + Perpetuals 余额
// 重要Spot 只加到總資產,不加到可用餘額
// 原因Spot 和 Perpetuals 是獨立帳戶,需手動 ClassTransfer 才能轉帳
totalWalletBalance := walletBalanceWithoutUnrealized + spotUSDCBalance
result["totalWalletBalance"] = totalWalletBalance // 總資產Perp + Spot
result["availableBalance"] = availableBalance // 可用餘額(僅 Perpetuals不含 Spot
result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未實現盈虧(僅來自 Perpetuals
result["spotBalance"] = spotUSDCBalance // Spot 現貨餘額(單獨返回)
log.Printf("✓ Hyperliquid 完整账户:")
log.Printf(" • Spot 现货余额: %.2f USDC (需手动转账到 Perpetuals 才能开仓)", spotUSDCBalance)
log.Printf(" • Perpetuals 合约净值: %.2f USDC (钱包%.2f + 未实现%.2f)",
accountValue,
walletBalanceWithoutUnrealized,
totalUnrealizedPnl,
result["availableBalance"],
totalMarginUsed)
totalUnrealizedPnl)
log.Printf(" • Perpetuals 可用余额: %.2f USDC (可直接用於開倉)", availableBalance)
log.Printf(" • 保证金占用: %.2f USDC", totalMarginUsed)
log.Printf(" • 總資產 (Perp+Spot): %.2f USDC", totalWalletBalance)
log.Printf(" ⭐ 总资产: %.2f USDC | Perp 可用: %.2f USDC | Spot 余额: %.2f USDC",
totalWalletBalance, availableBalance, spotUSDCBalance)
return result, nil
}
@@ -477,6 +549,25 @@ func (t *HyperliquidTrader) CloseShort(symbol string, quantity float64) (map[str
return result, nil
}
// CancelStopOrders 取消该币种的止盈/止
// CancelStopLossOrders 仅取消止损单Hyperliquid 暂无法区分止损和止盈,取消所有)
func (t *HyperliquidTrader) CancelStopLossOrders(symbol string) error {
// Hyperliquid SDK 的 OpenOrder 结构不暴露 trigger 字段
// 无法区分止损和止盈单,因此取消该币种的所有挂单
log.Printf(" ⚠️ Hyperliquid 无法区分止损/止盈单,将取消所有挂单")
return t.CancelStopOrders(symbol)
}
// CancelTakeProfitOrders 仅取消止盈单Hyperliquid 暂无法区分止损和止盈,取消所有)
func (t *HyperliquidTrader) CancelTakeProfitOrders(symbol string) error {
// Hyperliquid SDK 的 OpenOrder 结构不暴露 trigger 字段
// 无法区分止损和止盈单,因此取消该币种的所有挂单
log.Printf(" ⚠️ Hyperliquid 无法区分止损/止盈单,将取消所有挂单")
return t.CancelStopOrders(symbol)
}
// CancelAllOrders 取消该币种的所有挂单
func (t *HyperliquidTrader) CancelAllOrders(symbol string) error {
coin := convertSymbolToHyperliquid(symbol)
@@ -501,6 +592,40 @@ func (t *HyperliquidTrader) CancelAllOrders(symbol string) error {
return nil
}
// CancelStopOrders 取消该币种的止盈/止损单(用于调整止盈止损位置)
func (t *HyperliquidTrader) CancelStopOrders(symbol string) error {
coin := convertSymbolToHyperliquid(symbol)
// 获取所有挂单
openOrders, err := t.exchange.Info().OpenOrders(t.ctx, t.walletAddr)
if err != nil {
return fmt.Errorf("获取挂单失败: %w", err)
}
// 注意Hyperliquid SDK 的 OpenOrder 结构不暴露 trigger 字段
// 因此暂时取消该币种的所有挂单(包括止盈止损单)
// 这是安全的,因为在设置新的止盈止损之前,应该清理所有旧订单
canceledCount := 0
for _, order := range openOrders {
if order.Coin == coin {
_, err := t.exchange.Cancel(t.ctx, coin, order.Oid)
if err != nil {
log.Printf(" ⚠ 取消订单失败 (oid=%d): %v", order.Oid, err)
continue
}
canceledCount++
}
}
if canceledCount == 0 {
log.Printf(" %s 没有挂单需要取消", symbol)
} else {
log.Printf(" ✓ 已取消 %s 的 %d 个挂单(包括止盈/止损单)", symbol, canceledCount)
}
return nil
}
// GetMarketPrice 获取市场价格
func (t *HyperliquidTrader) GetMarketPrice(symbol string) (float64, error) {
coin := convertSymbolToHyperliquid(symbol)