Skip to content

Commit fd8ae74

Browse files
committed
Version 0.6.0
1 parent e87806e commit fd8ae74

14 files changed

+346
-88
lines changed

examples/basic_adding.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
TypedDict,
2020
Union,
2121
UserEventsMsg,
22+
UserEventsSubscription,
2223
)
2324
import example_utils
2425

@@ -56,9 +57,10 @@ class BasicAdder:
5657
def __init__(self, address: str, info: Info, exchange: Exchange):
5758
self.info = info
5859
self.exchange = exchange
59-
subscription: L2BookSubscription = {"type": "l2Book", "coin": COIN}
60-
self.info.subscribe(subscription, self.on_book_update)
61-
self.info.subscribe({"type": "userEvents", "user": address}, self.on_user_events)
60+
l2_book_subscription: L2BookSubscription = {"type": "l2Book", "coin": COIN}
61+
self.info.subscribe(l2_book_subscription, self.on_book_update)
62+
user_events_subscription: UserEventsSubscription = {"type": "userEvents", "user": address}
63+
self.info.subscribe(user_events_subscription, self.on_user_events)
6264
self.position: Optional[float] = None
6365
self.provide_state: Dict[Side, ProvideState] = {
6466
"A": {"type": "cancelled"},

examples/basic_spot_order.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
PURR = "PURR/USDC"
88
OTHER_COIN = "@8"
9+
OTHER_COIN_NAME = "KORILA/USDC"
910

1011

1112
def main():
@@ -44,7 +45,8 @@ def main():
4445
if order_result["status"] == "ok":
4546
status = order_result["response"]["data"]["statuses"][0]
4647
if "resting" in status:
47-
cancel_result = exchange.cancel(OTHER_COIN, status["resting"]["oid"])
48+
# The sdk now also support using spot names, although be careful as they might not always be unique
49+
cancel_result = exchange.cancel(OTHER_COIN_NAME, status["resting"]["oid"])
4850
print(cancel_result)
4951

5052

examples/basic_ws.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from hyperliquid.utils import constants
2+
import example_utils
3+
4+
5+
def main():
6+
address, info, _ = example_utils.setup(constants.TESTNET_API_URL)
7+
# An example showing how to subscribe to the different subscription types and prints the returned messages
8+
# Some subscriptions do not return snapshots, so you will not receive a message until something happens
9+
info.subscribe({"type": "allMids"}, print)
10+
info.subscribe({"type": "l2Book", "coin": "ETH"}, print)
11+
info.subscribe({"type": "trades", "coin": "PURR/USDC"}, print)
12+
info.subscribe({"type": "userEvents", "user": address}, print)
13+
info.subscribe({"type": "userFills", "user": address}, print)
14+
info.subscribe({"type": "candle", "coin": "ETH", "interval": "1m"}, print)
15+
info.subscribe({"type": "orderUpdates", "user": address}, print)
16+
info.subscribe({"type": "userFundings", "user": address}, print)
17+
info.subscribe({"type": "userNonFundingLedgerUpdates", "user": address}, print)
18+
19+
20+
if __name__ == "__main__":
21+
main()

hyperliquid/exchange.py

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,7 @@ def __init__(
4545
self.wallet = wallet
4646
self.vault_address = vault_address
4747
self.account_address = account_address
48-
self.info = Info(base_url, skip_ws=True)
49-
if meta is None:
50-
self.meta = self.info.meta()
51-
else:
52-
self.meta = meta
53-
54-
if spot_meta is None:
55-
self.spot_meta = self.info.spot_meta()
56-
else:
57-
self.spot_meta = spot_meta
58-
59-
self.coin_to_asset = {asset_info["name"]: asset for (asset, asset_info) in enumerate(self.meta["universe"])}
60-
61-
# spot assets start at 10000
62-
for spot_info in self.spot_meta["universe"]:
63-
self.coin_to_asset[spot_info["name"]] = spot_info["index"] + 10000
48+
self.info = Info(base_url, True, meta, spot_meta)
6449

6550
def _post_action(self, action, signature, nonce):
6651
payload = {
@@ -74,17 +59,18 @@ def _post_action(self, action, signature, nonce):
7459

7560
def _slippage_price(
7661
self,
77-
coin: str,
62+
name: str,
7863
is_buy: bool,
7964
slippage: float,
8065
px: Optional[float] = None,
8166
) -> float:
67+
coin = self.info.name_to_coin[name]
8268
if not px:
8369
# Get midprice
8470
px = float(self.info.all_mids()[coin])
8571

8672
# spot assets start at 10000
87-
is_spot = self.coin_to_asset[coin] >= 10_000
73+
is_spot = self.info.coin_to_asset[coin] >= 10_000
8874

8975
# Calculate Slippage
9076
px *= (1 + slippage) if is_buy else (1 - slippage)
@@ -93,7 +79,7 @@ def _slippage_price(
9379

9480
def order(
9581
self,
96-
coin: str,
82+
name: str,
9783
is_buy: bool,
9884
sz: float,
9985
limit_px: float,
@@ -102,7 +88,7 @@ def order(
10288
cloid: Optional[Cloid] = None,
10389
) -> Any:
10490
order: OrderRequest = {
105-
"coin": coin,
91+
"coin": name,
10692
"is_buy": is_buy,
10793
"sz": sz,
10894
"limit_px": limit_px,
@@ -115,7 +101,7 @@ def order(
115101

116102
def bulk_orders(self, order_requests: List[OrderRequest]) -> Any:
117103
order_wires: List[OrderWire] = [
118-
order_request_to_order_wire(order, self.coin_to_asset[order["coin"]]) for order in order_requests
104+
order_request_to_order_wire(order, self.info.name_to_asset(order["coin"])) for order in order_requests
119105
]
120106
timestamp = get_timestamp_ms()
121107

@@ -138,7 +124,7 @@ def bulk_orders(self, order_requests: List[OrderRequest]) -> Any:
138124
def modify_order(
139125
self,
140126
oid: OidOrCloid,
141-
coin: str,
127+
name: str,
142128
is_buy: bool,
143129
sz: float,
144130
limit_px: float,
@@ -149,7 +135,7 @@ def modify_order(
149135
modify: ModifyRequest = {
150136
"oid": oid,
151137
"order": {
152-
"coin": coin,
138+
"coin": name,
153139
"is_buy": is_buy,
154140
"sz": sz,
155141
"limit_px": limit_px,
@@ -165,7 +151,7 @@ def bulk_modify_orders_new(self, modify_requests: List[ModifyRequest]) -> Any:
165151
modify_wires = [
166152
{
167153
"oid": modify["oid"].to_raw() if isinstance(modify["oid"], Cloid) else modify["oid"],
168-
"order": order_request_to_order_wire(modify["order"], self.coin_to_asset[modify["order"]["coin"]]),
154+
"order": order_request_to_order_wire(modify["order"], self.info.name_to_asset(modify["order"]["coin"])),
169155
}
170156
for modify in modify_requests
171157
]
@@ -191,17 +177,17 @@ def bulk_modify_orders_new(self, modify_requests: List[ModifyRequest]) -> Any:
191177

192178
def market_open(
193179
self,
194-
coin: str,
180+
name: str,
195181
is_buy: bool,
196182
sz: float,
197183
px: Optional[float] = None,
198184
slippage: float = DEFAULT_SLIPPAGE,
199185
cloid: Optional[Cloid] = None,
200186
) -> Any:
201187
# Get aggressive Market Price
202-
px = self._slippage_price(coin, is_buy, slippage, px)
188+
px = self._slippage_price(name, is_buy, slippage, px)
203189
# Market Order is an aggressive Limit Order IoC
204-
return self.order(coin, is_buy, sz, px, order_type={"limit": {"tif": "Ioc"}}, reduce_only=False, cloid=cloid)
190+
return self.order(name, is_buy, sz, px, order_type={"limit": {"tif": "Ioc"}}, reduce_only=False, cloid=cloid)
205191

206192
def market_close(
207193
self,
@@ -230,19 +216,19 @@ def market_close(
230216
# Market Order is an aggressive Limit Order IoC
231217
return self.order(coin, is_buy, sz, px, order_type={"limit": {"tif": "Ioc"}}, reduce_only=True, cloid=cloid)
232218

233-
def cancel(self, coin: str, oid: int) -> Any:
234-
return self.bulk_cancel([{"coin": coin, "oid": oid}])
219+
def cancel(self, name: str, oid: int) -> Any:
220+
return self.bulk_cancel([{"coin": name, "oid": oid}])
235221

236-
def cancel_by_cloid(self, coin: str, cloid: Cloid) -> Any:
237-
return self.bulk_cancel_by_cloid([{"coin": coin, "cloid": cloid}])
222+
def cancel_by_cloid(self, name: str, cloid: Cloid) -> Any:
223+
return self.bulk_cancel_by_cloid([{"coin": name, "cloid": cloid}])
238224

239225
def bulk_cancel(self, cancel_requests: List[CancelRequest]) -> Any:
240226
timestamp = get_timestamp_ms()
241227
cancel_action = {
242228
"type": "cancel",
243229
"cancels": [
244230
{
245-
"a": self.coin_to_asset[cancel["coin"]],
231+
"a": self.info.name_to_asset(cancel["coin"]),
246232
"o": cancel["oid"],
247233
}
248234
for cancel in cancel_requests
@@ -269,7 +255,7 @@ def bulk_cancel_by_cloid(self, cancel_requests: List[CancelByCloidRequest]) -> A
269255
"type": "cancelByCloid",
270256
"cancels": [
271257
{
272-
"asset": self.coin_to_asset[cancel["coin"]],
258+
"asset": self.info.name_to_asset(cancel["coin"]),
273259
"cloid": cancel["cloid"].to_raw(),
274260
}
275261
for cancel in cancel_requests
@@ -316,12 +302,11 @@ def schedule_cancel(self, time: Optional[int]) -> Any:
316302
timestamp,
317303
)
318304

319-
def update_leverage(self, leverage: int, coin: str, is_cross: bool = True) -> Any:
305+
def update_leverage(self, leverage: int, name: str, is_cross: bool = True) -> Any:
320306
timestamp = get_timestamp_ms()
321-
asset = self.coin_to_asset[coin]
322307
update_leverage_action = {
323308
"type": "updateLeverage",
324-
"asset": asset,
309+
"asset": self.info.name_to_asset(name),
325310
"isCross": is_cross,
326311
"leverage": leverage,
327312
}
@@ -338,13 +323,12 @@ def update_leverage(self, leverage: int, coin: str, is_cross: bool = True) -> An
338323
timestamp,
339324
)
340325

341-
def update_isolated_margin(self, amount: float, coin: str) -> Any:
326+
def update_isolated_margin(self, amount: float, name: str) -> Any:
342327
timestamp = get_timestamp_ms()
343-
asset = self.coin_to_asset[coin]
344328
amount = float_to_usd_int(amount)
345329
update_isolated_margin_action = {
346330
"type": "updateIsolatedMargin",
347-
"asset": asset,
331+
"asset": self.info.name_to_asset(name),
348332
"isBuy": True,
349333
"ntli": amount,
350334
}

hyperliquid/info.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,34 @@
1414

1515

1616
class Info(API):
17-
def __init__(self, base_url=None, skip_ws=False):
17+
def __init__(
18+
self,
19+
base_url: Optional[str] = None,
20+
skip_ws: Optional[bool] = False,
21+
meta: Optional[Meta] = None,
22+
spot_meta: Optional[SpotMeta] = None,
23+
):
1824
super().__init__(base_url)
1925
if not skip_ws:
2026
self.ws_manager = WebsocketManager(self.base_url)
2127
self.ws_manager.start()
28+
if meta is None:
29+
meta = self.meta()
30+
31+
if spot_meta is None:
32+
spot_meta = self.spot_meta()
33+
34+
self.coin_to_asset = {asset_info["name"]: asset for (asset, asset_info) in enumerate(meta["universe"])}
35+
self.name_to_coin = {asset_info["name"]: asset_info["name"] for asset_info in meta["universe"]}
36+
37+
# spot assets start at 10000
38+
for spot_info in spot_meta["universe"]:
39+
self.coin_to_asset[spot_info["name"]] = spot_info["index"] + 10000
40+
self.name_to_coin[spot_info["name"]] = spot_info["name"]
41+
base, quote = spot_info["tokens"]
42+
name = f'{spot_meta["tokens"][base]["name"]}/{spot_meta["tokens"][quote]["name"]}'
43+
if name not in self.name_to_coin:
44+
self.name_to_coin[name] = spot_info["name"]
2245

2346
def user_state(self, address: str) -> Any:
2447
"""Retrieve trading details about a user.
@@ -290,13 +313,13 @@ def spot_meta_and_asset_ctxs(self) -> SpotMetaAndAssetCtxs:
290313
"""
291314
return cast(SpotMetaAndAssetCtxs, self.post("/info", {"type": "spotMetaAndAssetCtxs"}))
292315

293-
def funding_history(self, coin: str, startTime: int, endTime: Optional[int] = None) -> Any:
316+
def funding_history(self, name: str, startTime: int, endTime: Optional[int] = None) -> Any:
294317
"""Retrieve funding history for a given coin
295318
296319
POST /info
297320
298321
Args:
299-
coin (str): Coin to retrieve funding history for.
322+
name (str): Coin to retrieve funding history for.
300323
startTime (int): Unix timestamp in milliseconds.
301324
endTime (int): Unix timestamp in milliseconds.
302325
@@ -311,6 +334,7 @@ def funding_history(self, coin: str, startTime: int, endTime: Optional[int] = No
311334
...
312335
]
313336
"""
337+
coin = self.name_to_coin[name]
314338
if endTime is not None:
315339
return self.post(
316340
"/info", {"type": "fundingHistory", "coin": coin, "startTime": startTime, "endTime": endTime}
@@ -335,13 +359,13 @@ def user_funding_history(self, user: str, startTime: int, endTime: Optional[int]
335359
return self.post("/info", {"type": "userFunding", "user": user, "startTime": startTime, "endTime": endTime})
336360
return self.post("/info", {"type": "userFunding", "user": user, "startTime": startTime})
337361

338-
def l2_snapshot(self, coin: str) -> Any:
362+
def l2_snapshot(self, name: str) -> Any:
339363
"""Retrieve L2 snapshot for a given coin
340364
341365
POST /info
342366
343367
Args:
344-
coin (str): Coin to retrieve L2 snapshot for.
368+
name (str): Coin to retrieve L2 snapshot for.
345369
346370
Returns:
347371
{
@@ -360,15 +384,15 @@ def l2_snapshot(self, coin: str) -> Any:
360384
time: int
361385
}
362386
"""
363-
return self.post("/info", {"type": "l2Book", "coin": coin})
387+
return self.post("/info", {"type": "l2Book", "coin": self.name_to_coin[name]})
364388

365-
def candles_snapshot(self, coin: str, interval: str, startTime: int, endTime: int) -> Any:
389+
def candles_snapshot(self, name: str, interval: str, startTime: int, endTime: int) -> Any:
366390
"""Retrieve candles snapshot for a given coin
367391
368392
POST /info
369393
370394
Args:
371-
coin (str): Coin to retrieve candles snapshot for.
395+
name (str): Coin to retrieve candles snapshot for.
372396
interval (str): Candlestick interval.
373397
startTime (int): Unix timestamp in milliseconds.
374398
endTime (int): Unix timestamp in milliseconds.
@@ -390,7 +414,7 @@ def candles_snapshot(self, coin: str, interval: str, startTime: int, endTime: in
390414
...
391415
]
392416
"""
393-
req = {"coin": coin, "interval": interval, "startTime": startTime, "endTime": endTime}
417+
req = {"coin": self.name_to_coin[name], "interval": interval, "startTime": startTime, "endTime": endTime}
394418
return self.post("/info", {"type": "candleSnapshot", "req": req})
395419

396420
def user_fees(self, address: str) -> Any:
@@ -449,13 +473,20 @@ def query_sub_accounts(self, user: str) -> Any:
449473
return self.post("/info", {"type": "subAccounts", "user": user})
450474

451475
def subscribe(self, subscription: Subscription, callback: Callable[[Any], None]) -> int:
476+
if subscription["type"] == "l2Book" or subscription["type"] == "trades" or subscription["type"] == "candle":
477+
subscription["coin"] = self.name_to_coin[subscription["coin"]]
452478
if self.ws_manager is None:
453479
raise RuntimeError("Cannot call subscribe since skip_ws was used")
454480
else:
455481
return self.ws_manager.subscribe(subscription, callback)
456482

457483
def unsubscribe(self, subscription: Subscription, subscription_id: int) -> bool:
484+
if subscription["type"] == "l2Book" or subscription["type"] == "trades" or subscription["type"] == "candle":
485+
subscription["coin"] = self.name_to_coin[subscription["coin"]]
458486
if self.ws_manager is None:
459487
raise RuntimeError("Cannot call unsubscribe since skip_ws was used")
460488
else:
461489
return self.ws_manager.unsubscribe(subscription, subscription_id)
490+
491+
def name_to_asset(self, name: str) -> int:
492+
return self.coin_to_asset[self.name_to_coin[name]]

0 commit comments

Comments
 (0)