From eea26d755dc9e4df78cda81ba2705a5a116f508e Mon Sep 17 00:00:00 2001 From: tangmengqiu <1124090103@qq.com> Date: Mon, 3 Nov 2025 23:15:38 -0500 Subject: [PATCH] feat(hyperliquid): Auto-generate wallet address from private key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable automatic wallet address generation from private key for Hyperliquid exchange, simplifying user onboarding and reducing configuration errors. Backend Changes (trader/hyperliquid_trader.go): - Import crypto/ecdsa package for ECDSA public key operations - Enable wallet address auto-generation when walletAddr is empty - Use crypto.PubkeyToAddress() to derive address from private key - Add logging for both auto-generated and manually provided addresses Frontend Changes (web/src/components/AITradersPage.tsx): - Remove wallet address required validation (only private key required) - Update button disabled state to only check private key - Add "Optional" label to wallet address field - Add dynamic placeholder with bilingual hint - Show context-aware helper text based on input state - Remove HTML required attribute from input field Translation Updates (web/src/i18n/translations.ts): - Add 'optional' translation (EN: "Optional", ZH: "可选") - Add 'hyperliquidWalletAddressAutoGenerate' translation EN: "Leave blank to automatically generate wallet address from private key" ZH: "留空将自动从私钥生成钱包地址" Benefits: ✅ Simplified UX - Users only need to provide private key ✅ Error prevention - Auto-generated address always matches private key ✅ Backward compatible - Manual address input still supported ✅ Better UX - Clear visual indicators for optional fields Technical Details: - Uses Ethereum standard ECDSA public key to address conversion - Implementation was already present but commented out (lines 37-43) - No database schema changes required (hyperliquid_wallet_addr already nullable) - Fallback behavior: manual input > auto-generation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- trader/hyperliquid_trader.go | 20 +++++++++++++------- web/src/components/AITradersPage.tsx | 16 ++++++++++------ web/src/i18n/translations.ts | 4 ++++ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/trader/hyperliquid_trader.go b/trader/hyperliquid_trader.go index c189dbdc..0c7684d3 100644 --- a/trader/hyperliquid_trader.go +++ b/trader/hyperliquid_trader.go @@ -2,6 +2,7 @@ package trader import ( "context" + "crypto/ecdsa" "encoding/json" "fmt" "log" @@ -34,13 +35,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() diff --git a/web/src/components/AITradersPage.tsx b/web/src/components/AITradersPage.tsx index 41b3cdc2..f3364551 100644 --- a/web/src/components/AITradersPage.tsx +++ b/web/src/components/AITradersPage.tsx @@ -1201,7 +1201,7 @@ function ExchangeConfigModal({ if (!apiKey.trim() || !secretKey.trim()) return; await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), testnet); } else if (selectedExchange?.id === 'hyperliquid') { - if (!apiKey.trim() || !hyperliquidWalletAddr.trim()) return; + if (!apiKey.trim()) return; // 只验证私钥,钱包地址可选(会自动生成) await onSave(selectedExchangeId, apiKey.trim(), '', testnet, hyperliquidWalletAddr.trim()); } else if (selectedExchange?.id === 'aster') { if (!asterUser.trim() || !asterSigner.trim() || !asterPrivateKey.trim()) return; @@ -1360,18 +1360,22 @@ function ExchangeConfigModal({
setHyperliquidWalletAddr(e.target.value)} - placeholder={t('enterWalletAddress', language)} + placeholder="0x... (留空将自动从私钥生成 / Leave blank to auto-generate)" className="w-full px-3 py-2 rounded" style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }} - required />
- {t('hyperliquidWalletAddressDesc', language)} + {hyperliquidWalletAddr.trim() + ? t('hyperliquidWalletAddressDesc', language) + : t('hyperliquidWalletAddressAutoGenerate', language)}
@@ -1468,10 +1472,10 @@ function ExchangeConfigModal({