Skip to content

Commit ff3f784

Browse files
Merge pull request #17 from ignimbrite/feat/clientOrderId-support
Exchange API: Add Cloid Support for Order Placing and Cancellation
2 parents eb00557 + b13388b commit ff3f784

File tree

5 files changed

+72
-16
lines changed

5 files changed

+72
-16
lines changed

hyperliquid/consts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package hyperliquid
22

3-
const GLOBAL_DEBUG = false // Defualt debug that is used in all tests
3+
const GLOBAL_DEBUG = false // Default debug that is used in all tests
44

55
// Execution constants
66
const DEFAULT_SLIPPAGE = 0.005 // 0.5% default slippage

hyperliquid/convert.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func OrderRequestToWire(req OrderRequest, meta map[string]AssetInfo, isSpot bool
6060
SizePx: FloatToWire(req.Sz, maxDecimals, info.SzDecimals),
6161
ReduceOnly: req.ReduceOnly,
6262
OrderType: OrderTypeToWire(req.OrderType),
63+
Cloid: req.Cloid,
6364
}
6465
}
6566

hyperliquid/exchange_service.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ type IExchangeAPI interface {
1414
// Open orders
1515
BulkOrders(requests []OrderRequest, grouping Grouping) (*PlaceOrderResponse, error)
1616
Order(request OrderRequest, grouping Grouping) (*PlaceOrderResponse, error)
17-
MarketOrder(coin string, size float64, slippage *float64) (*PlaceOrderResponse, error)
18-
LimitOrder(orderType string, coin string, size float64, px float64, isBuy bool, reduceOnly bool) (*PlaceOrderResponse, error)
17+
MarketOrder(coin string, size float64, slippage *float64, clientOID ...string) (*PlaceOrderResponse, error)
18+
LimitOrder(orderType string, coin string, size float64, px float64, isBuy bool, reduceOnly bool, clientOID ...string) (*PlaceOrderResponse, error)
1919

2020
// Order management
2121
CancelOrderByOID(coin string, orderID int) (any, error)
22+
CancelOrderByCloid(coin string, clientOID string) (any, error)
2223
BulkCancelOrders(cancels []CancelOidWire) (any, error)
2324
CancelAllOrdersByCoin(coin string) (any, error)
2425
CancelAllOrders() (any, error)
@@ -98,7 +99,7 @@ func (api *ExchangeAPI) SlippagePriceSpot(coin string, isBuy bool, slippage floa
9899
// MarketOrder("BTC", 0.1, nil) // Buy 0.1 BTC
99100
// MarketOrder("BTC", -0.1, nil) // Sell 0.1 BTC
100101
// MarketOrder("BTC", 0.1, &slippage) // Buy 0.1 BTC with slippage
101-
func (api *ExchangeAPI) MarketOrder(coin string, size float64, slippage *float64) (*PlaceOrderResponse, error) {
102+
func (api *ExchangeAPI) MarketOrder(coin string, size float64, slippage *float64, clientOID ...string) (*PlaceOrderResponse, error) {
102103
slpg := GetSlippage(slippage)
103104
isBuy := IsBuy(size)
104105
finalPx := api.SlippagePrice(coin, isBuy, slpg)
@@ -115,6 +116,9 @@ func (api *ExchangeAPI) MarketOrder(coin string, size float64, slippage *float64
115116
OrderType: orderType,
116117
ReduceOnly: false,
117118
}
119+
if len(clientOID) > 0 {
120+
orderRequest.Cloid = clientOID[0]
121+
}
118122
return api.Order(orderRequest, GroupingNa)
119123
}
120124

@@ -150,7 +154,7 @@ func (api *ExchangeAPI) MarketOrderSpot(coin string, size float64, slippage *flo
150154
// Order type can be Gtc, Ioc, Alo.
151155
// Size determines the amount of the coin to buy/sell.
152156
// See the constants TifGtc, TifIoc, TifAlo.
153-
func (api *ExchangeAPI) LimitOrder(orderType string, coin string, size float64, px float64, reduceOnly bool) (*PlaceOrderResponse, error) {
157+
func (api *ExchangeAPI) LimitOrder(orderType string, coin string, size float64, px float64, reduceOnly bool, clientOID ...string) (*PlaceOrderResponse, error) {
154158
// check if the order type is valid
155159
if orderType != TifGtc && orderType != TifIoc && orderType != TifAlo {
156160
return nil, APIError{Message: fmt.Sprintf("Invalid order type: %s. Available types: %s, %s, %s", orderType, TifGtc, TifIoc, TifAlo)}
@@ -168,6 +172,9 @@ func (api *ExchangeAPI) LimitOrder(orderType string, coin string, size float64,
168172
OrderType: orderTypeZ,
169173
ReduceOnly: reduceOnly,
170174
}
175+
if len(clientOID) > 0 {
176+
orderRequest.Cloid = clientOID[0]
177+
}
171178
return api.Order(orderRequest, GroupingNa)
172179
}
173180

@@ -304,6 +311,33 @@ func (api *ExchangeAPI) CancelOrderByOID(coin string, orderID int64) (*CancelOrd
304311
return api.BulkCancelOrders([]CancelOidWire{{Asset: api.meta[coin].AssetId, Oid: int(orderID)}})
305312
}
306313

314+
// Cancel exact order by Client Order Id
315+
// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
316+
func (api *ExchangeAPI) CancelOrderByCloid(coin string, clientOID string) (*CancelOrderResponse, error) {
317+
timestamp := GetNonce()
318+
action := CancelCloidOrderAction{
319+
Type: "cancelByCloid",
320+
Cancels: []CancelCloidWire{
321+
{
322+
Asset: api.meta[coin].AssetId,
323+
Cloid: clientOID,
324+
},
325+
},
326+
}
327+
v, r, s, err := api.SignL1Action(action, timestamp)
328+
if err != nil {
329+
api.debug("Error signing L1 action: %s", err)
330+
return nil, err
331+
}
332+
request := ExchangeRequest{
333+
Action: action,
334+
Nonce: timestamp,
335+
Signature: ToTypedSig(r, s, v),
336+
VaultAddress: nil,
337+
}
338+
return MakeUniversalRequest[CancelOrderResponse](api, request)
339+
}
340+
307341
// Cancel all orders for a given coin
308342
func (api *ExchangeAPI) CancelAllOrdersByCoin(coin string) (*CancelOrderResponse, error) {
309343
orders, err := api.infoAPI.GetOpenOrders(api.AccountAddress())

hyperliquid/exchange_types.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type OrderRequest struct {
2828
LimitPx float64 `json:"limit_px"`
2929
OrderType OrderType `json:"order_type"`
3030
ReduceOnly bool `json:"reduce_only"`
31+
Cloid string `json:"cloid,omitempty"`
3132
}
3233

3334
type OrderType struct {
@@ -145,6 +146,16 @@ type CancelOidWire struct {
145146
Oid int `msgpack:"o" json:"o"`
146147
}
147148

149+
type CancelCloidWire struct {
150+
Asset int `msgpack:"asset" json:"asset"`
151+
Cloid string `msgpack:"cloid" json:"cloid"`
152+
}
153+
154+
type CancelCloidOrderAction struct {
155+
Type string `msgpack:"type" json:"type"`
156+
Cancels []CancelCloidWire `msgpack:"cancels" json:"cancels"`
157+
}
158+
148159
type CancelOrderResponse struct {
149160
Status string `json:"status"`
150161
Response InnerCancelResponse `json:"response"`
@@ -160,7 +171,8 @@ type CancelResponseStatuses struct {
160171
}
161172

162173
type RestingStatus struct {
163-
OrderId int `json:"oid"`
174+
OrderId int `json:"oid"`
175+
Cloid string `json:"cloid,omitempty"`
164176
}
165177

166178
type CloseRequest struct {
@@ -175,6 +187,7 @@ type FilledStatus struct {
175187
OrderId int `json:"oid"`
176188
AvgPx float64 `json:"avgPx,string"`
177189
TotalSz float64 `json:"totalSz,string"`
190+
Cloid string `json:"cloid,omitempty"`
178191
}
179192

180193
type Liquidation struct {

hyperliquid/hyperliquid_test.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,38 +70,46 @@ func TestHyperliquid_MakeSomeTradingLogic(t *testing.T) {
7070
}
7171
t.Logf("LimitOrder(TifGtc, ETH, -0.01, 5000.1, true): %v", res3)
7272

73+
res4, err := client.LimitOrder(TifGtc, "ETH", 0.01, 1234.1, false, "0x1234567890abcdef1234567890abcdef")
74+
if err != nil {
75+
if err != nil {
76+
t.Errorf("Error: %v", err)
77+
}
78+
}
79+
t.Logf("LimitOrder(TifIoc, ETH, 0.01, 1234.1, false, 0x1234567890abcdef1234567890abcdef): %v", res4)
80+
7381
// Get all ordres
74-
res4, err := client.GetAccountOpenOrders()
82+
res5, err := client.GetAccountOpenOrders()
7583
if err != nil {
7684
t.Errorf("Error: %v", err)
7785
}
78-
t.Logf("GetAccountOpenOrders(): %v", res4)
86+
t.Logf("GetAccountOpenOrders(): %v", res5)
7987

8088
// Close all orders
81-
res5, err := client.CancelAllOrders()
89+
res6, err := client.CancelAllOrders()
8290
if err != nil {
8391
t.Errorf("Error: %v", err)
8492
}
85-
t.Logf("CancelAllOrders(): %v", res5)
93+
t.Logf("CancelAllOrders(): %v", res6)
8694

8795
// Make market order
88-
res6, err := client.MarketOrder("ETH", 0.01, nil)
96+
res7, err := client.MarketOrder("ETH", 0.01, nil)
8997
if err != nil {
9098
t.Errorf("Error: %v", err)
9199
}
92-
t.Logf("MarketOrder(ETH, 0.01, nil): %v", res6)
100+
t.Logf("MarketOrder(ETH, 0.01, nil): %v", res7)
93101

94102
// Close position
95-
res7, err := client.ClosePosition("ETH")
103+
res8, err := client.ClosePosition("ETH")
96104
if err != nil {
97105
t.Errorf("Error: %v", err)
98106
}
99-
t.Logf("ClosePosition(ETH): %v", res7)
107+
t.Logf("ClosePosition(ETH): %v", res8)
100108

101109
// Get account balance
102-
res8, err := client.GetAccountState()
110+
res9, err := client.GetAccountState()
103111
if err != nil {
104112
t.Errorf("Error: %v", err)
105113
}
106-
t.Logf("GetAccountState(): %v", res8)
114+
t.Logf("GetAccountState(): %v", res9)
107115
}

0 commit comments

Comments
 (0)