Files
nofx/trader/lighter_trader_v2.go
0xYYBB | ZYY | Bobo 8dffff60a2 feat(lighter): 完整集成 LIGHTER DEX - SDK + 前端配置 UI (#1085)
* feat(trader): add LIGHTER DEX integration (initial implementation)

Add pure Go implementation of LIGHTER DEX trader following NOFX architecture

Features:
-  Account management with Ethereum wallet authentication
-  Order operations: market/limit orders, cancel, query
-  Position & balance queries
-  Zero-fee trading support (Standard accounts)
-  Up to 50x leverage for BTC/ETH

Implementation:
- Pure Go (no CGO dependencies) for easy deployment
- Based on hyperliquid_trader.go architecture
- Uses Ethereum ECDSA signatures (like Hyperliquid)
- API base URL: https://mainnet.zklighter.elliot.ai

Files:
- lighter_trader.go: Core trader structure & auth
- lighter_orders.go: Order management (create/cancel/query)
- lighter_account.go: Balance & position queries

Status: ⚠️ Partial implementation
-  Core structure complete
- ⏸️ Auth token generation needs implementation
- ⏸️ Transaction signing logic needs completion
- ⏸️ Config integration pending

Next steps:
1. Complete auth token generation
2. Add to config/exchange registry
3. Add frontend UI support
4. Create test suite

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

Co-Authored-By: tinkle-community <tinklefund@gmail.com>

* feat: Add LIGHTER DEX integration (快速整合階段)

## 🚀 新增功能
-  添加 LIGHTER DEX 作為第四個支持的交易所 (Binance, Hyperliquid, Aster, LIGHTER)
-  完整的數據庫配置支持(ExchangeConfig 新增 LighterWalletAddr, LighterPrivateKey 字段)
-  交易所註冊與初始化(initDefaultData 註冊 "lighter")
-  TraderManager 集成(配置傳遞邏輯完成)
-  AutoTrader 支持(NewAutoTrader 添加 "lighter" case)

## 📝 實現細節

### 後端整合
1. **數據庫層** (config/database.go):
   - ExchangeConfig 添加 LIGHTER 字段
   - 創建表時添加 lighter_wallet_addr, lighter_private_key 欄位
   - ALTER TABLE 語句用於向後兼容
   - UpdateExchange/CreateExchange/GetExchanges 支持 LIGHTER
   - migrateExchangesTable 支持 LIGHTER 字段

2. **API 層** (api/server.go, api/utils.go):
   - UpdateExchangeConfigRequest 添加 LIGHTER 字段
   - SanitizeExchangeConfigForLog 添加脫敏處理

3. **Trader 層** (trader/):
   - lighter_trader.go: 核心結構、認證、初始化
   - lighter_account.go: 餘額、持倉、市場價格查詢
   - lighter_orders.go: 訂單管理(創建、取消、查詢)
   - lighter_trading.go: 交易功能實現(開多/空、平倉、止損/盈)
   - 實現完整 Trader interface (13個方法)

4. **Manager 層** (manager/trader_manager.go):
   - addTraderFromDB 添加 LIGHTER 配置設置
   - AutoTraderConfig 添加 LIGHTER 字段

### 實現的功能(快速整合階段)
 基礎交易功能 (OpenLong, OpenShort, CloseLong, CloseShort)
 餘額查詢 (GetBalance, GetAccountBalance)
 持倉查詢 (GetPositions, GetPosition)
 訂單管理 (CreateOrder, CancelOrder, CancelAllOrders)
 止損/止盈 (SetStopLoss, SetTakeProfit, CancelStopLossOrders)
 市場數據 (GetMarketPrice)
 格式化工具 (FormatQuantity)

## ⚠️ TODO(完整實現階段)
- [ ] 完整認證令牌生成邏輯 (refreshAuthToken)
- [ ] 完整交易簽名邏輯(參考 Python SDK)
- [ ] 從 API 獲取幣種精度
- [ ] 區分止損/止盈訂單類型
- [ ] 前端 UI 支持
- [ ] 完整測試套件

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

Co-Authored-By: tinkle-community <tinklefund@gmail.com>

* feat: 完整集成 LIGHTER DEX with SDK

- 集成官方 lighter-go SDK (v0.0.0-20251104171447-78b9b55ebc48)
- 集成 Poseidon2 Goldilocks 簽名庫 (poseidon_crypto v0.0.11)
- 實現完整的 LighterTraderV2 使用官方 SDK
- 實現 17 個 Trader 接口方法(賬戶、交易、訂單管理)
- 支持雙密鑰系統(L1 錢包 + API Key)
- V1/V2 自動切換機制(向後兼容)
- 自動認證令牌管理(8小時有效期)
- 添加完整集成文檔 LIGHTER_INTEGRATION.md

新增文件:
- trader/lighter_trader_v2.go - V2 核心結構和初始化
- trader/lighter_trader_v2_account.go - 賬戶查詢方法
- trader/lighter_trader_v2_trading.go - 交易操作方法
- trader/lighter_trader_v2_orders.go - 訂單管理方法
- LIGHTER_INTEGRATION.md - 完整文檔

修改文件:
- trader/auto_trader.go - 添加 LighterAPIKeyPrivateKey 配置
- config/database.go - 添加 API Key 字段支持
- go.mod, go.sum - 添加 SDK 依賴

🤖 Generated with Claude Code

Co-Authored-By: tinkle-community <tinklefund@gmail.com>

* feat(lighter): 實現完整 HTTP 調用與動態市場映射

### 實現的功能

#### 1. submitOrder() - 真實訂單提交
- 使用 POST /api/v1/sendTx 提交已簽名訂單
- tx_type: 14 (CREATE_ORDER)
- 價格保護機制 (price_protection)
- 完整錯誤處理與響應解析

#### 2. GetActiveOrders() - 查詢活躍訂單
- GET /api/v1/accountActiveOrders
- 使用認證令牌 (Authorization header)
- 支持按市場索引過濾

#### 3. CancelOrder() - 真實取消訂單
- 使用 SDK 簽名 CancelOrderTxReq
- POST /api/v1/sendTx with tx_type: 15 (CANCEL_ORDER)
- 自動 nonce 管理

#### 4. getMarketIndex() - 動態市場映射
- 從 GET /api/v1/orderBooks 獲取市場列表
- 內存緩存 (marketIndexMap) 提高性能
- 回退到硬編碼映射(API 失敗時)
- 線程安全 (sync.RWMutex)

### 技術實現

**數據結構**:
- SendTxRequest/SendTxResponse - sendTx 請求響應
- MarketInfo - 市場信息緩存

**並發安全**:
- marketMutex - 保護市場索引緩存
- 讀寫鎖優化性能

**錯誤處理**:
- API 失敗回退機制
- 詳細日誌記錄
- HTTP 狀態碼驗證

### 測試

 編譯通過 (CGO_ENABLED=1)
 所有 Trader 接口方法實現完整
 HTTP 調用格式符合 LIGHTER API 規範

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

Co-Authored-By: tinkle-community <tinklefund@gmail.com>

* feat(lighter): 數據庫遷移與前端類型支持

### 數據庫變更

#### 新增欄位
- `exchanges.lighter_api_key_private_key` TEXT DEFAULT ''
- 支持 LIGHTER V2 的 40 字節 API Key 私鑰

#### 遷移腳本
- 📄 `migrations/002_add_lighter_api_key.sql`
- 包含完整的驗證和統計查詢
- 向後兼容現有配置(默認為空,使用 V1)

#### Schema 更新
- `config/database.go`:
  - 更新 CREATE TABLE 語句
  - 更新 exchanges_new 表結構
  - 新增 ALTER TABLE 遷移命令

### 前端類型更新

#### types.ts
- 新增 `Exchange` 接口字段:
  - `lighterWalletAddr?: string` - L1 錢包地址
  - `lighterPrivateKey?: string` - L1 私鑰
  - `lighterApiKeyPrivateKey?: string` - API Key 私鑰(新增)

### 技術細節

**數據庫兼容性**:
- 使用 ALTER TABLE ADD COLUMN IF NOT EXISTS
- 默認值為空字符串
- 不影響現有數據

**類型安全**:
- TypeScript 可選字段
- 與後端 ExchangeConfig 結構對齊

### 下一步

 **待完成**:
1. ExchangeConfigModal 組件更新
2. API 調用參數傳遞
3. V1/V2 狀態顯示

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

Co-Authored-By: tinkle-community <tinklefund@gmail.com>

* docs(lighter): 更新 LIGHTER_INTEGRATION.md 文檔狀態

* feat(lighter): 前端完整實現 - API Key 配置與 V1/V2 狀態

**英文**:
- `lighterWalletAddress`, `lighterPrivateKey`, `lighterApiKeyPrivateKey`
- `lighterWalletAddressDesc`, `lighterPrivateKeyDesc`, `lighterApiKeyPrivateKeyDesc`
- `lighterApiKeyOptionalNote` - V1 模式提示
- `lighterV1Description`, `lighterV2Description` - 狀態說明
- `lighterPrivateKeyImported` - 導入成功提示

**中文(繁體)**:
- 完整的中文翻譯對應
- 專業術語保留原文(L1、API Key、Poseidon2)

**Exchange 接口**:
- `lighterWalletAddr?: string`
- `lighterPrivateKey?: string`
- `lighterApiKeyPrivateKey?: string`

**UpdateExchangeConfigRequest 接口**:
- `lighter_wallet_addr?: string`
- `lighter_private_key?: string`
- `lighter_api_key_private_key?: string`

**狀態管理**:
- 添加 3 個 LIGHTER 狀態變量
- 更新 `secureInputTarget` 類型包含 'lighter'

**表單字段**:
- L1 錢包地址(必填,text input)
- L1 私鑰(必填,password + 安全輸入)
- API Key 私鑰(可選,password,40 字節)

**V1/V2 狀態顯示**:
- 動態背景顏色(V1: 橙色 #3F2E0F,V2: 綠色 #0F3F2E)
- 圖標指示(V1: ⚠️,V2: )
- 狀態說明文字

**驗證邏輯**:
- 必填字段:錢包地址 + L1 私鑰
- API Key 為可選字段
- 自動 V1/V2 檢測

**安全輸入**:
- 支持通過 TwoStageKeyModal 安全導入私鑰
- 導入成功後顯示 toast 提示

**handleSaveExchange**:
- 添加 3 個 LIGHTER 參數
- 更新交易所對象(新增/更新)
- 構建 API 請求(snake_case 字段)

**V1 模式(無 API Key)**:
```
┌────────────────────────────────────────┐
│ ⚠️ LIGHTER V1                          │
│ 基本模式 - 功能受限,僅用於測試框架       │
└────────────────────────────────────────┘
背景: #3F2E0F (橙色調)
邊框: #F59E0B (橙色)
```

**V2 模式(有 API Key)**:
```
┌────────────────────────────────────────┐
│  LIGHTER V2                          │
│ 完整模式 - 支持 Poseidon2 簽名和真實交易 │
└────────────────────────────────────────┘
背景: #0F3F2E (綠色調)
邊框: #10B981 (綠色)
```

1. **類型安全**
   - 完整的 TypeScript 類型定義
   - Props 接口正確對齊
   -  無 LIGHTER 相關編譯錯誤

2. **用戶體驗**
   - 清晰的必填/可選字段區分
   - 實時 V1/V2 狀態反饋
   - 安全私鑰輸入支持

3. **向後兼容**
   - 不影響現有交易所配置
   - 所有字段為可選(Optional)
   - API 請求格式統一

 TypeScript 編譯通過(無 LIGHTER 錯誤)
 類型定義完整且正確
 所有必需文件已更新
 與後端 API 格式對齊

Modified:
- `web/src/i18n/translations.ts` - 中英文翻譯
- `web/src/types.ts` - 類型定義
- `web/src/components/traders/ExchangeConfigModal.tsx` - Modal 組件
- `web/src/hooks/useTraderActions.ts` - Actions hook

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

Co-Authored-By: tinkle-community <tinklefund@gmail.com>

* test(lighter): 添加 V1 測試套件與修復 SafeFloat64 缺失

- 新增 trader/helpers.go: 添加 SafeFloat64/SafeString/SafeInt 輔助函數
- 新增 trader/lighter_trader_test.go: LIGHTER V1 測試套件
  -  測試通過 (7/10):
    - NewTrader 驗證 (無效私鑰, 有效私鑰格式)
    - FormatQuantity
    - GetExchangeType
    - InvalidQuantity 驗證
    - InvalidLeverage 驗證
    - HelperFunctions (SafeFloat64)
  - ⚠️ 待改進 (3/10):
    - GetBalance (需要調整 mock 響應格式)
    - GetPositions (需要調整 mock 響應格式)
    - GetMarketPrice (需要調整 mock 響應格式)

- 修復 Bug: lighter_account.go 和 lighter_trader_v2_account.go 中未定義的 SafeFloat64
- 測試框架: httptest.Server mock LIGHTER API
- 安全: 使用固定測試私鑰 (不含真實資金)

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

Co-Authored-By: tinkle-community <tinklefund@gmail.com>

---------

Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-20 19:29:01 +08:00

280 lines
8.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package trader
import (
"context"
"crypto/ecdsa"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
"sync"
"time"
lighterClient "github.com/elliottech/lighter-go/client"
lighterHTTP "github.com/elliottech/lighter-go/client/http"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)
// AccountInfo LIGHTER 賬戶信息
type AccountInfo struct {
AccountIndex int64 `json:"account_index"`
L1Address string `json:"l1_address"`
// 其他字段可以根據實際 API 響應添加
}
// LighterTraderV2 使用官方 lighter-go SDK 的新實現
type LighterTraderV2 struct {
ctx context.Context
privateKey *ecdsa.PrivateKey // L1 錢包私鑰(用於識別賬戶)
walletAddr string // Ethereum 錢包地址
client *http.Client
baseURL string
testnet bool
chainID uint32
// SDK 客戶端
httpClient lighterClient.MinimalHTTPClient
txClient *lighterClient.TxClient
// API Key 管理
apiKeyPrivateKey string // 40字節的 API Key 私鑰(用於簽名交易)
apiKeyIndex uint8 // API Key 索引(默認 0
accountIndex int64 // 賬戶索引
// 認證令牌
authToken string
tokenExpiry time.Time
accountMutex sync.RWMutex
// 市場信息緩存
symbolPrecision map[string]SymbolPrecision
precisionMutex sync.RWMutex
// 市場索引緩存
marketIndexMap map[string]uint8 // symbol -> market_id
marketMutex sync.RWMutex
}
// NewLighterTraderV2 創建新的 LIGHTER 交易器(使用官方 SDK
// 參數說明:
// - l1PrivateKeyHex: L1 錢包私鑰32字節標準以太坊私鑰
// - walletAddr: 以太坊錢包地址(可選,會從私鑰自動派生)
// - apiKeyPrivateKeyHex: API Key 私鑰40字節用於簽名交易如果為空則需要生成
// - testnet: 是否使用測試網
func NewLighterTraderV2(l1PrivateKeyHex, walletAddr, apiKeyPrivateKeyHex string, testnet bool) (*LighterTraderV2, error) {
// 1. 解析 L1 私鑰
l1PrivateKeyHex = strings.TrimPrefix(strings.ToLower(l1PrivateKeyHex), "0x")
l1PrivateKey, err := crypto.HexToECDSA(l1PrivateKeyHex)
if err != nil {
return nil, fmt.Errorf("無效的 L1 私鑰: %w", err)
}
// 2. 如果沒有提供錢包地址,從私鑰派生
if walletAddr == "" {
walletAddr = crypto.PubkeyToAddress(*l1PrivateKey.Public().(*ecdsa.PublicKey)).Hex()
log.Printf("✓ 從私鑰派生錢包地址: %s", walletAddr)
}
// 3. 確定 API URL 和 Chain ID
baseURL := "https://mainnet.zklighter.elliot.ai"
chainID := uint32(42766) // Mainnet Chain ID
if testnet {
baseURL = "https://testnet.zklighter.elliot.ai"
chainID = uint32(42069) // Testnet Chain ID
}
// 4. 創建 HTTP 客戶端
httpClient := lighterHTTP.NewClient(baseURL)
trader := &LighterTraderV2{
ctx: context.Background(),
privateKey: l1PrivateKey,
walletAddr: walletAddr,
client: &http.Client{Timeout: 30 * time.Second},
baseURL: baseURL,
testnet: testnet,
chainID: chainID,
httpClient: httpClient,
apiKeyPrivateKey: apiKeyPrivateKeyHex,
apiKeyIndex: 0, // 默認使用索引 0
symbolPrecision: make(map[string]SymbolPrecision),
marketIndexMap: make(map[string]uint8),
}
// 5. 初始化賬戶(獲取賬戶索引)
if err := trader.initializeAccount(); err != nil {
return nil, fmt.Errorf("初始化賬戶失敗: %w", err)
}
// 6. 如果沒有 API Key提示用戶需要生成
if apiKeyPrivateKeyHex == "" {
log.Printf("⚠️ 未提供 API Key 私鑰,請調用 GenerateAndRegisterAPIKey() 生成")
log.Printf(" 或者從 LIGHTER 官網獲取現有的 API Key")
return trader, nil
}
// 7. 創建 TxClient用於簽名交易
txClient, err := lighterClient.NewTxClient(
httpClient,
apiKeyPrivateKeyHex,
trader.accountIndex,
trader.apiKeyIndex,
trader.chainID,
)
if err != nil {
return nil, fmt.Errorf("創建 TxClient 失敗: %w", err)
}
trader.txClient = txClient
// 8. 驗證 API Key 是否正確
if err := trader.checkClient(); err != nil {
log.Printf("⚠️ API Key 驗證失敗: %v", err)
log.Printf(" 您可能需要重新生成 API Key 或檢查配置")
return trader, err
}
log.Printf("✓ LIGHTER 交易器初始化成功 (account=%d, apiKey=%d, testnet=%v)",
trader.accountIndex, trader.apiKeyIndex, testnet)
return trader, nil
}
// initializeAccount 初始化賬戶信息(獲取賬戶索引)
func (t *LighterTraderV2) initializeAccount() error {
// 通過 L1 地址獲取賬戶信息
accountInfo, err := t.getAccountByL1Address()
if err != nil {
return fmt.Errorf("獲取賬戶信息失敗: %w", err)
}
t.accountMutex.Lock()
t.accountIndex = accountInfo.AccountIndex
t.accountMutex.Unlock()
log.Printf("✓ 賬戶索引: %d", t.accountIndex)
return nil
}
// getAccountByL1Address 通過 L1 錢包地址獲取 LIGHTER 賬戶信息
func (t *LighterTraderV2) getAccountByL1Address() (*AccountInfo, error) {
endpoint := fmt.Sprintf("%s/api/v1/account?by=address&value=%s", t.baseURL, t.walletAddr)
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return nil, err
}
resp, err := t.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("獲取賬戶失敗 (status %d): %s", resp.StatusCode, string(body))
}
var accountInfo AccountInfo
if err := json.Unmarshal(body, &accountInfo); err != nil {
return nil, fmt.Errorf("解析賬戶響應失敗: %w", err)
}
return &accountInfo, nil
}
// checkClient 驗證 API Key 是否正確
func (t *LighterTraderV2) checkClient() error {
if t.txClient == nil {
return fmt.Errorf("TxClient 未初始化")
}
// 獲取服務器上註冊的 API Key 公鑰
publicKey, err := t.httpClient.GetApiKey(t.accountIndex, t.apiKeyIndex)
if err != nil {
return fmt.Errorf("獲取 API Key 失敗: %w", err)
}
// 獲取本地 API Key 的公鑰
pubKeyBytes := t.txClient.GetKeyManager().PubKeyBytes()
localPubKey := hexutil.Encode(pubKeyBytes[:])
localPubKey = strings.Replace(localPubKey, "0x", "", 1)
// 比對公鑰
if publicKey != localPubKey {
return fmt.Errorf("API Key 不匹配:本地=%s, 服務器=%s", localPubKey, publicKey)
}
log.Printf("✓ API Key 驗證通過")
return nil
}
// GenerateAndRegisterAPIKey 生成新的 API Key 並註冊到 LIGHTER
// 注意:這需要 L1 私鑰簽名,所以必須在有 L1 私鑰的情況下調用
func (t *LighterTraderV2) GenerateAndRegisterAPIKey(seed string) (privateKey, publicKey string, err error) {
// 這個功能需要調用官方 SDK 的 GenerateAPIKey 函數
// 但這是在 sharedlib 中的 CGO 函數,無法直接在純 Go 代碼中調用
//
// 解決方案:
// 1. 讓用戶從 LIGHTER 官網生成 API Key
// 2. 或者我們可以實現一個簡單的 API Key 生成包裝器
return "", "", fmt.Errorf("GenerateAndRegisterAPIKey 功能待實現,請從 LIGHTER 官網生成 API Key")
}
// refreshAuthToken 刷新認證令牌(使用官方 SDK
func (t *LighterTraderV2) refreshAuthToken() error {
if t.txClient == nil {
return fmt.Errorf("TxClient 未初始化,請先設置 API Key")
}
// 使用官方 SDK 生成認證令牌(有效期 7 小時)
deadline := time.Now().Add(7 * time.Hour)
authToken, err := t.txClient.GetAuthToken(deadline)
if err != nil {
return fmt.Errorf("生成認證令牌失敗: %w", err)
}
t.accountMutex.Lock()
t.authToken = authToken
t.tokenExpiry = deadline
t.accountMutex.Unlock()
log.Printf("✓ 認證令牌已生成(有效期至: %s", t.tokenExpiry.Format(time.RFC3339))
return nil
}
// ensureAuthToken 確保認證令牌有效
func (t *LighterTraderV2) ensureAuthToken() error {
t.accountMutex.RLock()
expired := time.Now().After(t.tokenExpiry.Add(-30 * time.Minute)) // 提前 30 分鐘刷新
t.accountMutex.RUnlock()
if expired {
log.Println("🔄 認證令牌即將過期,刷新中...")
return t.refreshAuthToken()
}
return nil
}
// GetExchangeType 獲取交易所類型
func (t *LighterTraderV2) GetExchangeType() string {
return "lighter"
}
// Cleanup 清理資源
func (t *LighterTraderV2) Cleanup() error {
log.Println("⏹ LIGHTER 交易器清理完成")
return nil
}