async-hyperliquid 0.4.1__tar.gz → 0.4.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/PKG-INFO +1 -1
  2. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/pyproject.toml +3 -2
  3. async_hyperliquid-0.4.3/src/async_hyperliquid/_async_hyperliquid/__init__.py +3 -0
  4. async_hyperliquid-0.4.3/src/async_hyperliquid/_async_hyperliquid/actions.py +283 -0
  5. async_hyperliquid-0.4.3/src/async_hyperliquid/_async_hyperliquid/core.py +239 -0
  6. async_hyperliquid-0.4.3/src/async_hyperliquid/_async_hyperliquid/info.py +234 -0
  7. async_hyperliquid-0.4.3/src/async_hyperliquid/_async_hyperliquid/orders.py +383 -0
  8. async_hyperliquid-0.4.3/src/async_hyperliquid/async_hyperliquid.py +214 -0
  9. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/src/async_hyperliquid/info.py +1 -1
  10. async_hyperliquid-0.4.1/src/async_hyperliquid/async_hyperliquid.py +0 -1138
  11. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/LICENSE +0 -0
  12. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/README.md +0 -0
  13. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/src/async_hyperliquid/__init__.py +0 -0
  14. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/src/async_hyperliquid/async_api.py +0 -0
  15. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/src/async_hyperliquid/exchange.py +0 -0
  16. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/src/async_hyperliquid/utils/__init__.py +0 -0
  17. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/src/async_hyperliquid/utils/constants.py +0 -0
  18. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/src/async_hyperliquid/utils/decorators.py +0 -0
  19. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/src/async_hyperliquid/utils/miscs.py +0 -0
  20. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/src/async_hyperliquid/utils/signing.py +0 -0
  21. {async_hyperliquid-0.4.1 → async_hyperliquid-0.4.3}/src/async_hyperliquid/utils/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: async-hyperliquid
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Async Hyperliquid client using aiohttp
5
5
  Keywords: dex,hyperliquid,async,aiohttp,trading,cryptocurrency,defi
6
6
  Author: Yuki
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "async-hyperliquid"
3
- version = "0.4.1"
3
+ version = "0.4.3"
4
4
  description = "Async Hyperliquid client using aiohttp"
5
5
  authors = [{ name = "Yuki", email = "yuqi.lyle@gmail.com" }]
6
6
  readme = "README.md"
@@ -35,7 +35,7 @@ classifiers = [
35
35
  ]
36
36
  requires-python = ">=3.10,<4"
37
37
  dependencies = [
38
- "aiohttp (>=3.11.12,<4.0.0)",
38
+ "aiohttp>=3.11.12,<4.0.0",
39
39
  "msgpack (>=1.1.0,<2.0.0)",
40
40
  "eth-account (>=0.13.5,<0.14.0)",
41
41
  "eth-utils (>=5.2.0,<6.0.0)",
@@ -57,6 +57,7 @@ dev = [
57
57
  "pre-commit>=4.1.0",
58
58
  "python-dotenv>=1.0.1",
59
59
  "hyperliquid-python-sdk>=0.19.0",
60
+ "ty>=0.0.22",
60
61
  ]
61
62
 
62
63
  [project.urls]
@@ -0,0 +1,3 @@
1
+ from .actions import AsyncHyperliquidActionsClient
2
+
3
+ __all__ = ["AsyncHyperliquidActionsClient"]
@@ -0,0 +1,283 @@
1
+ import math
2
+ import re
3
+ import warnings
4
+
5
+ from async_hyperliquid.utils.constants import (
6
+ HYPE_FACTOR,
7
+ MAINNET_API_URL,
8
+ USD_FACTOR,
9
+ )
10
+ from async_hyperliquid.utils.decorators import private_key_required
11
+ from async_hyperliquid.utils.miscs import get_timestamp_ms, round_token_amount
12
+ from async_hyperliquid.utils.signing import (
13
+ sign_approve_agent_action,
14
+ sign_approve_builder_fee_action,
15
+ sign_convert_to_multi_sig_user_action,
16
+ sign_send_asset_action,
17
+ sign_spot_transfer_action,
18
+ sign_staking_deposit_action,
19
+ sign_staking_withdraw_action,
20
+ sign_token_delegate_action,
21
+ sign_usd_class_transfer_action,
22
+ sign_usd_transfer_action,
23
+ sign_user_dex_abstraction_action,
24
+ sign_user_set_abstraction_action,
25
+ sign_withdraw_action,
26
+ )
27
+ from async_hyperliquid.utils.types import AgentAbstraction, UserSetAbstraction
28
+
29
+ from .orders import AsyncHyperliquidOrdersClient
30
+
31
+
32
+ class AsyncHyperliquidActionsClient(AsyncHyperliquidOrdersClient):
33
+ async def set_referrer_code(self, code: str):
34
+ action = {"type": "setReferrer", "code": code}
35
+ return await self.exchange.post_action(action)
36
+
37
+ @private_key_required
38
+ async def create_sub_account(self, name: str):
39
+ action = {"type": "createSubAccount", "name": name}
40
+ return await self.exchange.post_action(action)
41
+
42
+ @private_key_required
43
+ async def usd_transfer(self, amount: float, dest: str):
44
+ nonce = get_timestamp_ms()
45
+ action = {
46
+ "type": "usdSend",
47
+ "amount": round_token_amount(amount, 2),
48
+ "destination": dest,
49
+ "time": nonce,
50
+ }
51
+ is_mainnet = self.base_url == MAINNET_API_URL
52
+ sig = sign_usd_transfer_action(self.account, action, is_mainnet)
53
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
54
+
55
+ @private_key_required
56
+ async def spot_transfer(self, coin: str, amount: float, dest: str):
57
+ token_info = await self.get_token_info(coin)
58
+ token_name = token_info["name"]
59
+ token_id = token_info["tokenId"]
60
+ wei_decimals = token_info["weiDecimals"]
61
+ token = f"{token_name}:{token_id}"
62
+ nonce = get_timestamp_ms()
63
+ action = {
64
+ "type": "spotSend",
65
+ "destination": dest,
66
+ "token": token,
67
+ "amount": round_token_amount(amount, wei_decimals),
68
+ "time": nonce,
69
+ }
70
+ sig = sign_spot_transfer_action(self.account, action, self.is_mainnet)
71
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
72
+
73
+ @private_key_required
74
+ async def initiate_withdrawal(self, amount: float):
75
+ nonce = get_timestamp_ms()
76
+ action = {
77
+ "type": "withdraw3",
78
+ "amount": round_token_amount(amount, 2),
79
+ "time": nonce,
80
+ "destination": self.address,
81
+ }
82
+ sig = sign_withdraw_action(self.account, action, self.is_mainnet)
83
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
84
+
85
+ @private_key_required
86
+ async def usd_class_transfer(self, amount: float, to_perp: bool = False):
87
+ nonce = get_timestamp_ms()
88
+ action = {
89
+ "type": "usdClassTransfer",
90
+ "amount": round_token_amount(amount, 2),
91
+ "toPerp": to_perp,
92
+ "nonce": nonce,
93
+ }
94
+ sig = sign_usd_class_transfer_action(
95
+ self.account, action, self.base_url == MAINNET_API_URL
96
+ )
97
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
98
+
99
+ @private_key_required
100
+ async def send_asset(
101
+ self,
102
+ coin: str,
103
+ amount: float,
104
+ dest: str,
105
+ source_dex: str,
106
+ dest_dex: str,
107
+ sub_account: str = "",
108
+ ):
109
+ token_info = await self.get_token_info(coin)
110
+ token_name = token_info["name"]
111
+ token_id = token_info["tokenId"]
112
+ wei_decimals = token_info["weiDecimals"]
113
+ token = f"{token_name}:{token_id}"
114
+ nonce = get_timestamp_ms()
115
+ action = {
116
+ "type": "sendAsset",
117
+ "token": token,
118
+ "amount": round_token_amount(amount, wei_decimals),
119
+ "destination": dest,
120
+ "sourceDex": source_dex,
121
+ "destinationDex": dest_dex,
122
+ "fromSubAccount": sub_account,
123
+ "nonce": nonce,
124
+ }
125
+ sig = sign_send_asset_action(self.account, action, self.is_mainnet)
126
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
127
+
128
+ @private_key_required
129
+ async def staking_deposit(self, amount: float):
130
+ amount_in_wei = int(math.floor(amount * HYPE_FACTOR))
131
+ nonce = get_timestamp_ms()
132
+ action = {"type": "cDeposit", "wei": amount_in_wei, "nonce": nonce}
133
+ sig = sign_staking_deposit_action(self.account, action, self.is_mainnet)
134
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
135
+
136
+ @private_key_required
137
+ async def staking_withdraw(self, amount: float):
138
+ amount_in_wei = int(math.floor(amount * HYPE_FACTOR))
139
+ nonce = get_timestamp_ms()
140
+ action = {"type": "cWithdraw", "wei": amount_in_wei, "nonce": nonce}
141
+ sig = sign_staking_withdraw_action(
142
+ self.account, action, self.is_mainnet
143
+ )
144
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
145
+
146
+ @private_key_required
147
+ async def token_delegate(
148
+ self, validator: str, amount: float, is_undelegate: bool = False
149
+ ):
150
+ amount_in_wei = int(math.floor(amount * HYPE_FACTOR))
151
+ nonce = get_timestamp_ms()
152
+ action = {
153
+ "type": "tokenDelegate",
154
+ "validator": validator,
155
+ "wei": amount_in_wei,
156
+ "isUndelegate": is_undelegate,
157
+ "nonce": nonce,
158
+ }
159
+ sig = sign_token_delegate_action(self.account, action, self.is_mainnet)
160
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
161
+
162
+ @private_key_required
163
+ async def vault_transfer(
164
+ self, vault: str, amount: float, is_deposit: bool = True
165
+ ):
166
+ usd_amount = int(math.floor(amount * USD_FACTOR))
167
+ action = {
168
+ "type": "vaultTransfer",
169
+ "vaultAddress": vault,
170
+ "isDeposit": is_deposit,
171
+ "usd": usd_amount,
172
+ }
173
+ return await self.exchange.post_action(action)
174
+
175
+ async def approve_agent(self, agent: str, name: str | None = None):
176
+ nonce = get_timestamp_ms()
177
+ action = {
178
+ "type": "approveAgent",
179
+ "agentAddress": agent,
180
+ "agentName": name or "",
181
+ "nonce": nonce,
182
+ }
183
+ sig = sign_approve_agent_action(self.account, action, self.is_mainnet)
184
+ if name is None:
185
+ del action["agentName"]
186
+
187
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
188
+
189
+ async def approve_builder_fee(self, max_fee_rate: float, builder: str):
190
+ nonce = get_timestamp_ms()
191
+ action = {
192
+ "type": "approveBuilderFee",
193
+ "maxFeeRate": f"{max_fee_rate:.3%}",
194
+ "builder": builder,
195
+ "nonce": nonce,
196
+ }
197
+ sig = sign_approve_builder_fee_action(
198
+ self.account, action, self.is_mainnet
199
+ )
200
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
201
+
202
+ async def convert_to_multi_sig_user(self, users: list[str], threshold: int):
203
+ nonce = get_timestamp_ms()
204
+ signers = {"authorizedUsers": sorted(users), "threshold": threshold}
205
+ action = {
206
+ "type": "convertToMultiSigUser",
207
+ "signers": signers,
208
+ "nonce": nonce,
209
+ }
210
+ sig = sign_convert_to_multi_sig_user_action(
211
+ self.account, action, self.is_mainnet
212
+ )
213
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
214
+
215
+ async def reserve_request_weight(self, weight: int):
216
+ action = {"type": "reserveRequestWeight", "weight": weight}
217
+ return await self.exchange.post_action(action, expires=self.expires)
218
+
219
+ async def use_big_block(self, enable: bool):
220
+ action = {"type": "evmUserModify", "usingBigBlocks": enable}
221
+ return await self.exchange.post_action(action)
222
+
223
+ async def user_dex_abstraction(
224
+ self, user: str | None = None, enabled: bool = True
225
+ ):
226
+ warnings.warn(
227
+ "user_dex_abstraction is deprecated and may be removed in a "
228
+ "future release.",
229
+ DeprecationWarning,
230
+ stacklevel=2,
231
+ )
232
+ nonce = get_timestamp_ms()
233
+ if user is None:
234
+ user = self.address
235
+ action = {
236
+ "type": "userDexAbstraction",
237
+ "user": user.lower(),
238
+ "enabled": enabled,
239
+ "nonce": nonce,
240
+ }
241
+ sig = sign_user_dex_abstraction_action(
242
+ self.account, action, self.is_mainnet
243
+ )
244
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
245
+
246
+ async def user_set_abstraction(
247
+ self, abstraction: UserSetAbstraction, user: str | None = None
248
+ ):
249
+ nonce = get_timestamp_ms()
250
+ if user is None:
251
+ user = self.address
252
+ if re.fullmatch(r"0x[a-fA-F0-9]{40}", user) is None:
253
+ raise ValueError(
254
+ f"user must be a 42-char hex address, got: {user!r}"
255
+ )
256
+ action = {
257
+ "type": "userSetAbstraction",
258
+ "user": user.lower(),
259
+ "abstraction": abstraction,
260
+ "nonce": nonce,
261
+ }
262
+ sig = sign_user_set_abstraction_action(
263
+ self.account, action, self.is_mainnet
264
+ )
265
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
266
+
267
+ async def agent_enable_dex_abstraction(self):
268
+ warnings.warn(
269
+ "agent_enable_dex_abstraction is deprecated and may be removed "
270
+ "in a future release.",
271
+ DeprecationWarning,
272
+ stacklevel=2,
273
+ )
274
+ action = {"type": "agentEnableDexAbstraction"}
275
+ return await self.exchange.post_action(
276
+ action, vault=self.vault, expires=self.expires
277
+ )
278
+
279
+ async def agent_set_abstraction(self, abstraction: AgentAbstraction):
280
+ action = {"type": "agentSetAbstraction", "abstraction": abstraction}
281
+ return await self.exchange.post_action(
282
+ action, vault=self.vault, expires=self.expires
283
+ )
@@ -0,0 +1,239 @@
1
+ import asyncio
2
+
3
+ from aiohttp import ClientSession, ClientTimeout
4
+ from eth_account import Account
5
+ from eth_account.signers.local import LocalAccount
6
+ from hl_web3.exchange import Exchange as EVMExchange
7
+ from hl_web3.info import Info as EVMInfo
8
+ from hl_web3.utils.constants import HL_RPC_URL, HL_TESTNET_RPC_URL
9
+
10
+ from async_hyperliquid.async_api import AsyncAPI
11
+ from async_hyperliquid.exchange import ExchangeAPI
12
+ from async_hyperliquid.info import InfoAPI
13
+ from async_hyperliquid.utils.constants import (
14
+ MAINNET_API_URL,
15
+ PERP_DEX_OFFSET,
16
+ SPOT_OFFSET,
17
+ TESTNET_API_URL,
18
+ )
19
+ from async_hyperliquid.utils.types import (
20
+ Metas,
21
+ PerpMeta,
22
+ SpotMeta,
23
+ SpotTokenMeta,
24
+ )
25
+
26
+
27
+ class AsyncHyperliquidCore(AsyncAPI):
28
+ address: str
29
+ is_mainnet: bool
30
+ account: LocalAccount
31
+ session: ClientSession
32
+ base_url: str
33
+ vault: str | None
34
+
35
+ coin_assets: dict[str, int]
36
+ coin_names: dict[str, str]
37
+ coin_symbols: dict[str, str]
38
+ asset_sz_decimals: dict[int, int]
39
+ spot_tokens: dict[str, SpotTokenMeta]
40
+
41
+ perp_dexs: list[str]
42
+
43
+ enable_evm: bool
44
+ evm_info: EVMInfo
45
+ evm_exchange: EVMExchange
46
+
47
+ def __init__(
48
+ self,
49
+ address: str,
50
+ api_key: str,
51
+ is_mainnet: bool = True,
52
+ enable_evm: bool = False,
53
+ evm_rpc_url: str | None = None,
54
+ private_key: str | None = None,
55
+ vault: str | None = None,
56
+ perp_dexs: list[str] = [""],
57
+ ):
58
+ self.address = address
59
+ self.is_mainnet = is_mainnet
60
+ self.account = Account.from_key(api_key)
61
+ self.session = ClientSession(timeout=ClientTimeout(connect=3))
62
+ self.base_url = MAINNET_API_URL if is_mainnet else TESTNET_API_URL
63
+ self.info = InfoAPI(self.base_url, self.session)
64
+ self.exchange = ExchangeAPI(
65
+ self.account, self.session, self.base_url, address=self.address
66
+ )
67
+
68
+ self.coin_assets = {}
69
+ self.coin_names = {}
70
+ self.coin_symbols = {}
71
+ self.asset_sz_decimals = {}
72
+ self.spot_tokens = {}
73
+
74
+ self.vault = vault
75
+ self.expires: int | None = None
76
+ self.perp_dexs = perp_dexs
77
+
78
+ if enable_evm:
79
+ self._init_evm_client(private_key, evm_rpc_url)
80
+
81
+ def set_expires(self, expires: int | None) -> None:
82
+ self.expires = expires
83
+
84
+ def _init_evm_client(
85
+ self, private_key: str | None, rpc_url: str | None = None
86
+ ) -> None:
87
+ if rpc_url is None:
88
+ rpc_url = HL_RPC_URL if self.is_mainnet else HL_TESTNET_RPC_URL
89
+
90
+ self.evm_info = EVMInfo(rpc_url)
91
+
92
+ if private_key is None:
93
+ if self.account.address != self.address:
94
+ raise ValueError(
95
+ "EVM Exchange client can not init without private key"
96
+ )
97
+ private_key = self.account.key.hex()
98
+
99
+ self.evm_exchange = EVMExchange(rpc_url, private_key)
100
+
101
+ def _init_perp_meta(self, meta: PerpMeta, offset: int) -> None:
102
+ for asset, info in enumerate(meta["universe"]):
103
+ asset += offset
104
+ asset_name = info["name"]
105
+ self.coin_assets[asset_name] = asset
106
+ self.coin_names[asset_name] = asset_name
107
+ self.asset_sz_decimals[asset] = info["szDecimals"]
108
+
109
+ def _init_spot_meta(self, meta: SpotMeta) -> None:
110
+ total_tokens = len(meta["tokens"])
111
+ for info in meta["universe"]:
112
+ asset = info["index"] + SPOT_OFFSET
113
+ asset_name = info["name"]
114
+
115
+ self.coin_assets[asset_name] = asset
116
+ self.coin_names[asset_name] = asset_name
117
+
118
+ base, quote = info["tokens"]
119
+ if base >= total_tokens or quote >= total_tokens:
120
+ print("Unreconized token index for: ", info)
121
+ continue
122
+
123
+ base_info = meta["tokens"][base]
124
+ base_name = base_info["name"]
125
+ quote_name = meta["tokens"][quote]["name"]
126
+ name = f"{base_name}/{quote_name}"
127
+ if name not in self.coin_names:
128
+ self.coin_names[name] = asset_name
129
+
130
+ self.asset_sz_decimals[asset] = base_info["szDecimals"]
131
+ self.spot_tokens[asset_name] = meta["tokens"][base]
132
+
133
+ def _update_coin_symbols(self) -> None:
134
+ self.coin_symbols = {
135
+ v: k for k, v in self.coin_names.items() if not k.startswith("@")
136
+ }
137
+
138
+ async def init_metas(self) -> None:
139
+ meta_task = self.info.get_perp_meta()
140
+ spot_meta_task = self.info.get_spot_meta()
141
+ all_dex_names_task = self.get_all_dex_name()
142
+
143
+ meta, spot_meta, all_dex_names = await asyncio.gather(
144
+ meta_task, spot_meta_task, all_dex_names_task
145
+ )
146
+
147
+ self._init_perp_meta(meta, 0)
148
+ self._init_spot_meta(spot_meta)
149
+
150
+ dex_meta_tasks = []
151
+ dex_indices = []
152
+ for dex in self.perp_dexs:
153
+ if dex == "":
154
+ continue
155
+ try:
156
+ idx = all_dex_names.index(dex)
157
+ except ValueError:
158
+ continue
159
+
160
+ if idx > 0:
161
+ dex_meta_tasks.append(self.info.get_perp_meta(dex))
162
+ dex_indices.append(idx)
163
+
164
+ if dex_meta_tasks:
165
+ dex_metas = await asyncio.gather(*dex_meta_tasks)
166
+ for idx, dex_meta in zip(dex_indices, dex_metas):
167
+ dex_asset_offset = PERP_DEX_OFFSET + (idx - 1) * 10000
168
+ self._init_perp_meta(dex_meta, dex_asset_offset)
169
+
170
+ self._update_coin_symbols()
171
+
172
+ async def get_metas(self, perp_only: bool = False) -> Metas:
173
+ metas: Metas = {"perp": {}, "spot": [], "dexs": {}} # type: ignore
174
+ perp_meta = await self.info.get_perp_meta()
175
+ if perp_only:
176
+ metas["perp"] = perp_meta
177
+ return metas
178
+
179
+ metas["spot"] = await self.info.get_spot_meta()
180
+ return metas
181
+
182
+ async def get_all_metas(self) -> Metas:
183
+ dexs = await self.get_all_dex_name()
184
+ dex_metas = {}
185
+
186
+ for dex in dexs[1:]:
187
+ meta = await self.info.get_perp_meta(dex)
188
+ dex_metas[dex] = meta
189
+
190
+ spot_meta = await self.info.get_spot_meta()
191
+ perp_meta = await self.info.get_perp_meta()
192
+ return {"perp": perp_meta, "spot": spot_meta, "dexs": dex_metas}
193
+
194
+ async def get_all_dex_name(self) -> list[str]:
195
+ names = []
196
+ dexs = await self.info.get_perp_dexs()
197
+ for dex in dexs:
198
+ if dex is None:
199
+ names.append("")
200
+ else:
201
+ names.append(dex["name"])
202
+ return names
203
+
204
+ async def get_coin_name(self, coin: str) -> str:
205
+ if not hasattr(self, "coin_names") or coin not in self.coin_names:
206
+ await self.init_metas()
207
+
208
+ if coin not in self.coin_names:
209
+ raise ValueError(f"Coin {coin} not found")
210
+
211
+ return self.coin_names[coin]
212
+
213
+ async def get_coin_asset(self, coin: str) -> int:
214
+ coin_name = await self.get_coin_name(coin)
215
+
216
+ if coin_name not in self.coin_assets:
217
+ raise ValueError(f"Coin {coin}({coin_name}) not found")
218
+
219
+ return self.coin_assets[coin_name]
220
+
221
+ async def get_coin_symbol(self, coin: str) -> str:
222
+ coin_name = await self.get_coin_name(coin)
223
+ return self.coin_symbols[coin_name]
224
+
225
+ async def get_coin_sz_decimals(self, coin: str) -> int:
226
+ coin_name = await self.get_coin_name(coin)
227
+ asset = await self.get_coin_asset(coin_name)
228
+ return self.asset_sz_decimals[asset]
229
+
230
+ async def get_token_info(self, coin: str) -> SpotTokenMeta:
231
+ coin_name = await self.get_coin_name(coin)
232
+ return self.spot_tokens[coin_name]
233
+
234
+ async def get_token_id(self, coin: str) -> str:
235
+ token_info = await self.get_token_info(coin)
236
+ if not token_info:
237
+ raise ValueError(f"Token {coin} not found")
238
+
239
+ return token_info["tokenId"]