feat(hyperliquid): enhance Agent Wallet security model (#717)

## Background

Hyperliquid official documentation recommends using Agent Wallet pattern for API trading:
- Agent Wallet is used for signing only
- Main Wallet Address is used for querying account data
- Agent Wallet should not hold significant funds

Reference: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets

## Current Implementation

Current implementation allows auto-generating wallet address from private key,
which simplifies user configuration but may lead to potential security concerns
if users accidentally use their main wallet private key.

## Enhancement

Following the proven pattern already used in Aster exchange implementation
(which uses dual-address mode), this enhancement upgrades Hyperliquid to
Agent Wallet mode:

### Core Changes

1. **Mandatory dual-address configuration**
   - Agent Private Key (for signing)
   - Main Wallet Address (holds funds)

2. **Multi-layer security checks**
   - Detect if user accidentally uses main wallet private key
   - Validate Agent wallet balance (reject if > 100 USDC)
   - Provide detailed configuration guidance

3. **Design consistency**
   - Align with Aster's dual-address pattern
   - Follow Hyperliquid official best practices

### Code Changes

**config/database.go**:
- Add inline comments clarifying Agent Wallet security model

**trader/hyperliquid_trader.go**:
- Require explicit main wallet address (no auto-generation)
- Check if agent address matches main wallet address (security risk indicator)
- Query agent wallet balance and block if excessive
- Display both agent and main wallet addresses for transparency

**web/src/components/AITradersPage.tsx**:
- Add security alert banner explaining Agent Wallet mode
- Separate required inputs for Agent Private Key and Main Wallet Address
- Add field descriptions and validation

### Benefits

-  Aligns with Hyperliquid official security recommendations
-  Maintains design consistency with Aster implementation
-  Multi-layer protection against configuration mistakes
-  Detailed logging for troubleshooting

### Breaking Change

Users must now explicitly provide main wallet address (hyperliquid_wallet_addr).
Old configurations will receive clear error messages with migration guidance.

### Migration Guide

**Before** (single private key):
```json
{
  "hyperliquid_private_key": "0x..."
}
```

**After** (Agent Wallet mode):
```json
{
  "hyperliquid_private_key": "0x...",  // Agent Wallet private key
  "hyperliquid_wallet_addr": "0x..."   // Main Wallet address
}
```

Users can create Agent Wallet on Hyperliquid official website:
https://app.hyperliquid.xyz/ → Settings → API Wallets

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

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
0xYYBB | ZYY | Bobo
2025-11-07 23:26:56 +08:00
committed by GitHub
parent 28a63f4d48
commit b48dfe7bfd
3 changed files with 148 additions and 23 deletions

View File

@@ -39,17 +39,29 @@ func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool)
apiURL = hyperliquid.TestnetAPIURL
}
// 从私钥生成钱包地址(如果未提供)
// Security enhancement: Implement Agent Wallet best practices
// Reference: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets
agentAddr := crypto.PubkeyToAddress(*privateKey.Public().(*ecdsa.PublicKey)).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)
return nil, fmt.Errorf("❌ Configuration error: Main wallet address (hyperliquid_wallet_addr) not provided\n" +
"🔐 Correct configuration pattern:\n" +
" 1. hyperliquid_private_key = Agent Private Key (for signing only, balance should be ~0)\n" +
" 2. hyperliquid_wallet_addr = Main Wallet Address (holds funds, never expose private key)\n" +
"💡 Please create an Agent Wallet on Hyperliquid official website and authorize it before configuration:\n" +
" https://app.hyperliquid.xyz/ → Settings → API Wallets")
}
// Check if user accidentally uses main wallet private key (security risk)
if strings.EqualFold(walletAddr, agentAddr) {
log.Printf("⚠️⚠️⚠️ WARNING: Main wallet address (%s) matches Agent wallet address!", walletAddr)
log.Printf(" This indicates you may be using your main wallet private key, which poses extremely high security risks!")
log.Printf(" Recommendation: Immediately create a separate Agent Wallet on Hyperliquid official website")
log.Printf(" Reference: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets")
} else {
log.Printf("✓ 使用提供的钱包地址: %s", walletAddr)
log.Printf("✓ Using Agent Wallet mode (secure)")
log.Printf(" └─ Agent wallet address: %s (for signing)", agentAddr)
log.Printf(" └─ Main wallet address: %s (holds funds)", walletAddr)
}
ctx := context.Background()
@@ -73,6 +85,39 @@ func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool)
return nil, fmt.Errorf("获取meta信息失败: %w", err)
}
// 🔍 Security check: Validate Agent wallet balance (should be close to 0)
// Only check if using separate Agent wallet (not when main wallet is used as agent)
if !strings.EqualFold(walletAddr, agentAddr) {
agentState, err := exchange.Info().UserState(ctx, agentAddr)
if err == nil && agentState != nil && agentState.CrossMarginSummary != nil {
// Parse Agent wallet balance
agentBalance, _ := strconv.ParseFloat(agentState.CrossMarginSummary.AccountValue, 64)
if agentBalance > 100 {
// Critical: Agent wallet holds too much funds
log.Printf("🚨🚨🚨 CRITICAL SECURITY WARNING 🚨🚨🚨")
log.Printf(" Agent wallet balance: %.2f USDC (exceeds safe threshold of 100 USDC)", agentBalance)
log.Printf(" Agent wallet address: %s", agentAddr)
log.Printf(" ⚠️ Agent wallets should only be used for signing and hold minimal/zero balance")
log.Printf(" ⚠️ High balance in Agent wallet poses security risks")
log.Printf(" 📖 Reference: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets")
log.Printf(" 💡 Recommendation: Transfer funds to main wallet and keep Agent wallet balance near 0")
return nil, fmt.Errorf("security check failed: Agent wallet balance too high (%.2f USDC), exceeds 100 USDC threshold", agentBalance)
} else if agentBalance > 10 {
// Warning: Agent wallet has some balance (acceptable but not ideal)
log.Printf("⚠️ Notice: Agent wallet address (%s) has some balance: %.2f USDC", agentAddr, agentBalance)
log.Printf(" While not critical, it's recommended to keep Agent wallet balance near 0 for security")
} else {
// OK: Agent wallet balance is safe
log.Printf("✓ Agent wallet balance is safe: %.2f USDC (near zero as recommended)", agentBalance)
}
} else if err != nil {
// Failed to query agent balance - log warning but don't block initialization
log.Printf("⚠️ Could not verify Agent wallet balance (query failed): %v", err)
log.Printf(" Proceeding with initialization, but please manually verify Agent wallet balance is near 0")
}
}
return &HyperliquidTrader{
exchange: exchange,
ctx: ctx,