Skip to content

Commit d26850a

Browse files
committed
Info API: Added UserRole to auto set vaultAddress on startup
1 parent b2f2b2d commit d26850a

10 files changed

+401
-192
lines changed

hyperliquid/client.go

+32-7
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ type IClient interface {
2222
IAPIService
2323
SetPrivateKey(privateKey string) error
2424
SetAccountAddress(address string)
25+
SetVaultAddress(address string)
26+
SetUserRole(role Role)
2527
AccountAddress() string
28+
VaultAddress() string
2629
SetDebugActive()
2730
IsMainnet() bool
2831
}
@@ -33,14 +36,16 @@ type IClient interface {
3336
// the network type, the private key, and the logger.
3437
// The debug method prints the debug messages.
3538
type Client struct {
36-
baseUrl string // Base URL of the HyperLiquid API
39+
baseURL string // Base URL of the HyperLiquid API
3740
privateKey string // Private key for the client
38-
defualtAddress string // Default address for the client
41+
defaultAddress string // Default address for the client
3942
isMainnet bool // Network type
4043
Debug bool // Debug mode
4144
httpClient *http.Client // HTTP client
4245
keyManager *PKeyManager // Private key manager
4346
Logger *log.Logger // Logger for debug messages
47+
role Role // Role of the client,
48+
vaultAddress string // Vault address
4449
}
4550

4651
// Returns the private key manager connected to the API.
@@ -60,19 +65,21 @@ func getURL(isMainnet bool) string {
6065
// NewClient returns a new instance of the Client struct.
6166
func NewClient(isMainnet bool) *Client {
6267
logger := log.New()
68+
logger.SetLevel(log.DebugLevel)
6369
logger.SetFormatter(&log.TextFormatter{
6470
FullTimestamp: true,
6571
PadLevelText: true,
72+
ForceColors: true,
6673
})
6774
logger.SetOutput(os.Stdout)
6875
logger.SetLevel(log.DebugLevel)
6976
return &Client{
70-
baseUrl: getURL(isMainnet),
77+
baseURL: getURL(isMainnet),
7178
httpClient: http.DefaultClient,
7279
Debug: false,
7380
isMainnet: isMainnet,
7481
privateKey: "",
75-
defualtAddress: "",
82+
defaultAddress: "",
7683
Logger: logger,
7784
keyManager: nil,
7885
}
@@ -100,12 +107,30 @@ func (client *Client) SetPrivateKey(privateKey string) error {
100107
// In case you use PKeyManager from API section https://app.hyperliquid.xyz/API
101108
// Then you can use this method to set the address.
102109
func (client *Client) SetAccountAddress(address string) {
103-
client.defualtAddress = address
110+
client.defaultAddress = address
104111
}
105112

106113
// Returns the public address connected to the API.
107114
func (client *Client) AccountAddress() string {
108-
return client.defualtAddress
115+
return client.defaultAddress
116+
}
117+
118+
// VaultAddress returns the vault address for the client.
119+
func (client *Client) VaultAddress() string {
120+
return client.vaultAddress
121+
}
122+
123+
// SetVaultAddress sets the vault address for the client.
124+
func (client *Client) SetVaultAddress(vaultAddress string) {
125+
client.vaultAddress = vaultAddress
126+
}
127+
128+
// SetUserRole sets the user role for the client.
129+
func (client *Client) SetUserRole(role Role) {
130+
client.role = role
131+
if role.IsVaultOrSubAccount() {
132+
client.vaultAddress = client.AccountAddress()
133+
}
109134
}
110135

111136
// IsMainnet returns true if the client is connected to the mainnet.
@@ -121,7 +146,7 @@ func (client *Client) SetDebugActive() {
121146
// Request sends a POST request to the HyperLiquid API.
122147
func (client *Client) Request(endpoint string, payload any) ([]byte, error) {
123148
endpoint = strings.TrimPrefix(endpoint, "/") // Remove leading slash if present
124-
url := fmt.Sprintf("%s/%s", client.baseUrl, endpoint)
149+
url := fmt.Sprintf("%s/%s", client.baseURL, endpoint)
125150
client.debug("Request to %s", url)
126151
jsonPayload, err := json.Marshal(payload)
127152
if err != nil {

hyperliquid/convert.go

+96-35
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ func HexToBytes(addr string) []byte {
3434
return b
3535
}
3636
}
37+
func HexToInt(hexString string) (*big.Int, error) {
38+
value := new(big.Int)
39+
if len(hexString) > 1 && hexString[:2] == "0x" {
40+
hexString = hexString[2:]
41+
}
42+
_, success := value.SetString(hexString, 16)
43+
if !success {
44+
return nil, fmt.Errorf("invalid hexadecimal string: %s", hexString)
45+
}
46+
return value, nil
47+
}
48+
49+
func IntToHex(value *big.Int) string {
50+
return "0x" + value.Text(16)
51+
}
3752

3853
func OrderWiresToOrderAction(orders []OrderWire, grouping Grouping) PlaceOrderAction {
3954
return PlaceOrderAction{
@@ -43,19 +58,35 @@ func OrderWiresToOrderAction(orders []OrderWire, grouping Grouping) PlaceOrderAc
4358
}
4459
}
4560

46-
func OrderRequestToWire(req OrderRequest, meta map[string]AssetInfo, isSpot bool) OrderWire {
61+
func (req *OrderRequest) isSpot() bool {
62+
return strings.ContainsAny(req.Coin, "@-")
63+
}
64+
65+
// ToWire (OrderRequest) converts an OrderRequest to an OrderWire using the provided metadata.
66+
// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/asset-ids
67+
func (req *OrderRequest) ToWireMeta(meta map[string]AssetInfo) OrderWire {
4768
info := meta[req.Coin]
48-
var assetId, maxDecimals int
49-
if isSpot {
50-
// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/asset-ids
51-
assetId = info.AssetId + 10000
69+
return req.ToWire(info)
70+
}
71+
72+
// ToModifyWire converts an OrderRequest to a ModifyOrderWire using the provided AssetInfo.
73+
func (req *OrderRequest) ToModifyWire(info AssetInfo) ModifyOrderWire {
74+
return ModifyOrderWire{
75+
OrderID: *req.OrderID,
76+
Order: req.ToWire(info),
77+
}
78+
}
79+
80+
// ToWire converts an OrderRequest to an OrderWire using the provided AssetInfo.
81+
func (req *OrderRequest) ToWire(info AssetInfo) OrderWire {
82+
var assetID = info.AssetID
83+
var maxDecimals = PERP_MAX_DECIMALS
84+
if req.isSpot() {
85+
assetID = info.AssetID + 10000 // https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/asset-ids
5286
maxDecimals = SPOT_MAX_DECIMALS
53-
} else {
54-
assetId = info.AssetId
55-
maxDecimals = PERP_MAX_DECIMALS
5687
}
5788
return OrderWire{
58-
Asset: assetId,
89+
Asset: assetID,
5990
IsBuy: req.IsBuy,
6091
LimitPx: PriceToWire(req.LimitPx, maxDecimals, info.SzDecimals),
6192
SizePx: SizeToWire(req.Sz, info.SzDecimals),
@@ -65,30 +96,7 @@ func OrderRequestToWire(req OrderRequest, meta map[string]AssetInfo, isSpot bool
6596
}
6697
}
6798

68-
func ModifyOrderRequestToWire(req ModifyOrderRequest, meta map[string]AssetInfo, isSpot bool) ModifyOrderWire {
69-
info := meta[req.Coin]
70-
var assetId, maxDecimals int
71-
if isSpot {
72-
// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/asset-ids
73-
assetId = info.AssetId + 10000
74-
maxDecimals = SPOT_MAX_DECIMALS
75-
} else {
76-
assetId = info.AssetId
77-
maxDecimals = PERP_MAX_DECIMALS
78-
}
79-
return ModifyOrderWire{
80-
OrderId: req.OrderId,
81-
Order: OrderWire{
82-
Asset: assetId,
83-
IsBuy: req.IsBuy,
84-
LimitPx: PriceToWire(req.LimitPx, maxDecimals, info.SzDecimals),
85-
SizePx: SizeToWire(req.Sz, info.SzDecimals),
86-
ReduceOnly: req.ReduceOnly,
87-
OrderType: OrderTypeToWire(req.OrderType),
88-
},
89-
}
90-
}
91-
99+
// OrderTypeToWire converts an OrderType to an OrderTypeWire.
92100
func OrderTypeToWire(orderType OrderType) OrderTypeWire {
93101
if orderType.Limit != nil {
94102
return OrderTypeWire{
@@ -110,8 +118,26 @@ func OrderTypeToWire(orderType OrderType) OrderTypeWire {
110118
return OrderTypeWire{}
111119
}
112120

113-
// Format the float with custom decimal places, default is 6 (perp), 8 (spot).
114-
// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size
121+
/**
122+
* FloatToWire converts a float64 to a string representation following Hyperliquid's decimal rules.
123+
* FloatToWire converts a float64 to a string representation following Hyperliquid's decimal rules.
124+
*
125+
* The conversion adheres to market-specific decimal place constraints:
126+
* - Perpetual markets: Maximum 6 decimal places
127+
* - Spot markets: Maximum 8 decimal places
128+
*
129+
* The function dynamically adjusts decimal precision based on:
130+
* 1. Integer part magnitude
131+
* 2. Maximum allowed decimals (maxDecimals)
132+
* 3. Size decimal precision (szDecimals)
133+
*
134+
* Output formatting:
135+
* - Removes trailing zeros
136+
* - Trims unnecessary decimal points
137+
* - Maintains tick size precision requirements
138+
*
139+
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size
140+
*/
115141
func FloatToWire(x float64, maxDecimals int, szDecimals int) string {
116142
bigf := big.NewFloat(x)
117143
var maxDecSz uint
@@ -228,3 +254,38 @@ func StructToMap(strct any) (res map[string]interface{}, err error) {
228254
json.Unmarshal(a, &res)
229255
return res, nil
230256
}
257+
258+
// RoundOrderSize rounds the order size to the nearest tick size
259+
func RoundOrderSize(x float64, szDecimals int) string {
260+
newX := math.Round(x*math.Pow10(szDecimals)) / math.Pow10(szDecimals)
261+
// TODO: add rounding
262+
return big.NewFloat(newX).Text('f', szDecimals)
263+
}
264+
265+
// RoundOrderPrice rounds the order price to the nearest tick size
266+
func RoundOrderPrice(x float64, szDecimals int, maxDecimals int) string {
267+
maxSignFigures := 5
268+
allowedDecimals := maxDecimals - szDecimals
269+
numberOfDigitsInIntegerPart := len(strconv.Itoa(int(x)))
270+
if numberOfDigitsInIntegerPart >= maxSignFigures {
271+
return RoundOrderSize(x, 0)
272+
}
273+
allowedSignFigures := maxSignFigures - numberOfDigitsInIntegerPart
274+
if x >= 1 {
275+
return RoundOrderSize(x, min(allowedSignFigures, allowedDecimals))
276+
}
277+
278+
text := RoundOrderSize(x, allowedDecimals)
279+
startSignFigures := false
280+
for i := 2; i < len(text); i++ {
281+
if text[i] == '0' && !startSignFigures {
282+
continue
283+
}
284+
startSignFigures = true
285+
allowedSignFigures--
286+
if allowedSignFigures == 0 {
287+
return text[:i+1]
288+
}
289+
}
290+
return text
291+
}

0 commit comments

Comments
 (0)