feat(hyperliquid): Add Unified Account support for Spot as Perp collateral (#1387)

This PR adds support for Hyperliquid's Unified Account mode where Spot USDC
balance can be used as collateral for Perpetual trading.

Changes:
- Add HyperliquidUnifiedAcct field to Exchange config (default: true)
- Update HyperliquidTrader to support unified account mode
- When enabled, Spot USDC balance is added to available trading balance
- Update API request/response structs for unified account toggle
- Update trader config propagation from exchange config

This aligns with Hyperliquid's roadmap to make Unified Account the default.
This commit is contained in:
Hao Fu
2026-02-22 04:03:21 -05:00
committed by GitHub
parent 64935b9d47
commit 06d6080751
5 changed files with 58 additions and 28 deletions

View File

@@ -484,6 +484,7 @@ type UpdateExchangeConfigRequest struct {
Passphrase string `json:"passphrase"` // OKX specific
Testnet bool `json:"testnet"`
HyperliquidWalletAddr string `json:"hyperliquid_wallet_addr"`
HyperliquidUnifiedAcct bool `json:"hyperliquid_unified_account"` // Unified Account mode
AsterUser string `json:"aster_user"`
AsterSigner string `json:"aster_signer"`
AsterPrivateKey string `json:"aster_private_key"`
@@ -600,6 +601,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
string(exchangeCfg.APIKey), // private key
exchangeCfg.HyperliquidWalletAddr,
exchangeCfg.Testnet,
exchangeCfg.HyperliquidUnifiedAcct,
)
case "aster":
tempTrader, createErr = aster.NewAsterTrader(
@@ -1169,6 +1171,7 @@ func (s *Server) handleSyncBalance(c *gin.Context) {
string(exchangeCfg.APIKey),
exchangeCfg.HyperliquidWalletAddr,
exchangeCfg.Testnet,
exchangeCfg.HyperliquidUnifiedAcct,
)
case "aster":
tempTrader, createErr = aster.NewAsterTrader(
@@ -1332,6 +1335,7 @@ func (s *Server) handleClosePosition(c *gin.Context) {
string(exchangeCfg.APIKey),
exchangeCfg.HyperliquidWalletAddr,
exchangeCfg.Testnet,
exchangeCfg.HyperliquidUnifiedAcct,
)
case "aster":
tempTrader, createErr = aster.NewAsterTrader(
@@ -1906,7 +1910,7 @@ func (s *Server) handleUpdateExchangeConfigs(c *gin.Context) {
tradersToReload[t.ID] = true
}
err := s.store.Exchange().Update(userID, exchangeID, exchangeData.Enabled, exchangeData.APIKey, exchangeData.SecretKey, exchangeData.Passphrase, exchangeData.Testnet, exchangeData.HyperliquidWalletAddr, exchangeData.AsterUser, exchangeData.AsterSigner, exchangeData.AsterPrivateKey, exchangeData.LighterWalletAddr, exchangeData.LighterPrivateKey, exchangeData.LighterAPIKeyPrivateKey, exchangeData.LighterAPIKeyIndex)
err := s.store.Exchange().Update(userID, exchangeID, exchangeData.Enabled, exchangeData.APIKey, exchangeData.SecretKey, exchangeData.Passphrase, exchangeData.Testnet, exchangeData.HyperliquidWalletAddr, exchangeData.HyperliquidUnifiedAcct, exchangeData.AsterUser, exchangeData.AsterSigner, exchangeData.AsterPrivateKey, exchangeData.LighterWalletAddr, exchangeData.LighterPrivateKey, exchangeData.LighterAPIKeyPrivateKey, exchangeData.LighterAPIKeyIndex)
if err != nil {
SafeInternalError(c, fmt.Sprintf("Update exchange %s", exchangeID), err)
return
@@ -1940,6 +1944,7 @@ type CreateExchangeRequest struct {
Passphrase string `json:"passphrase"`
Testnet bool `json:"testnet"`
HyperliquidWalletAddr string `json:"hyperliquid_wallet_addr"`
HyperliquidUnifiedAcct bool `json:"hyperliquid_unified_account"` // Unified Account mode: Spot as Perp collateral
AsterUser string `json:"aster_user"`
AsterSigner string `json:"aster_signer"`
AsterPrivateKey string `json:"aster_private_key"`
@@ -2014,7 +2019,8 @@ func (s *Server) handleCreateExchange(c *gin.Context) {
id, err := s.store.Exchange().Create(
userID, req.ExchangeType, req.AccountName, req.Enabled,
req.APIKey, req.SecretKey, req.Passphrase, req.Testnet,
req.HyperliquidWalletAddr, req.AsterUser, req.AsterSigner, req.AsterPrivateKey,
req.HyperliquidWalletAddr, req.HyperliquidUnifiedAcct,
req.AsterUser, req.AsterSigner, req.AsterPrivateKey,
req.LighterWalletAddr, req.LighterPrivateKey, req.LighterAPIKeyPrivateKey, req.LighterAPIKeyIndex,
)
if err != nil {