exchange.py 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118
  1. import json
  2. import logging
  3. import secrets
  4. import eth_account
  5. from eth_account.signers.local import LocalAccount
  6. from hyperliquid.api import API
  7. from hyperliquid.info import Info
  8. from hyperliquid.utils.constants import MAINNET_API_URL
  9. from hyperliquid.utils.signing import (
  10. CancelByCloidRequest,
  11. CancelRequest,
  12. ModifyRequest,
  13. OidOrCloid,
  14. OrderRequest,
  15. OrderType,
  16. OrderWire,
  17. ScheduleCancelAction,
  18. float_to_usd_int,
  19. get_timestamp_ms,
  20. order_request_to_order_wire,
  21. order_wires_to_order_action,
  22. sign_agent,
  23. sign_approve_builder_fee,
  24. sign_convert_to_multi_sig_user_action,
  25. sign_l1_action,
  26. sign_multi_sig_action,
  27. sign_send_asset_action,
  28. sign_spot_transfer_action,
  29. sign_token_delegate_action,
  30. sign_usd_class_transfer_action,
  31. sign_usd_transfer_action,
  32. sign_withdraw_from_bridge_action,
  33. )
  34. from hyperliquid.utils.types import (
  35. Any,
  36. BuilderInfo,
  37. Cloid,
  38. Dict,
  39. List,
  40. Meta,
  41. Optional,
  42. PerpDexSchemaInput,
  43. SpotMeta,
  44. Tuple,
  45. )
  46. class Exchange(API):
  47. # Default Max Slippage for Market Orders 5%
  48. DEFAULT_SLIPPAGE = 0.05
  49. def __init__(
  50. self,
  51. wallet: LocalAccount,
  52. base_url: Optional[str] = None,
  53. meta: Optional[Meta] = None,
  54. vault_address: Optional[str] = None,
  55. account_address: Optional[str] = None,
  56. spot_meta: Optional[SpotMeta] = None,
  57. perp_dexs: Optional[List[str]] = None,
  58. timeout: Optional[float] = None,
  59. ):
  60. super().__init__(base_url, timeout)
  61. self.wallet = wallet
  62. self.vault_address = vault_address
  63. self.account_address = account_address
  64. self.info = Info(base_url, True, meta, spot_meta, perp_dexs, timeout)
  65. self.expires_after: Optional[int] = None
  66. def _post_action(self, action, signature, nonce):
  67. payload = {
  68. "action": action,
  69. "nonce": nonce,
  70. "signature": signature,
  71. "vaultAddress": self.vault_address if action["type"] not in ["usdClassTransfer", "sendAsset"] else None,
  72. "expiresAfter": self.expires_after,
  73. }
  74. logging.debug(payload)
  75. return self.post("/exchange", payload)
  76. def _slippage_price(
  77. self,
  78. name: str,
  79. is_buy: bool,
  80. slippage: float,
  81. px: Optional[float] = None,
  82. ) -> float:
  83. coin = self.info.name_to_coin[name]
  84. if not px:
  85. # Get midprice
  86. px = float(self.info.all_mids()[coin])
  87. asset = self.info.coin_to_asset[coin]
  88. # spot assets start at 10000
  89. is_spot = asset >= 10_000
  90. # Calculate Slippage
  91. px *= (1 + slippage) if is_buy else (1 - slippage)
  92. # We round px to 5 significant figures and 6 decimals for perps, 8 decimals for spot
  93. return round(float(f"{px:.5g}"), (6 if not is_spot else 8) - self.info.asset_to_sz_decimals[asset])
  94. # expires_after will cause actions to be rejected after that timestamp in milliseconds
  95. # expires_after is not supported on user_signed actions (e.g. usd_transfer) and must be None in order for those
  96. # actions to work.
  97. def set_expires_after(self, expires_after: Optional[int]) -> None:
  98. self.expires_after = expires_after
  99. def order(
  100. self,
  101. name: str,
  102. is_buy: bool,
  103. sz: float,
  104. limit_px: float,
  105. order_type: OrderType,
  106. reduce_only: bool = False,
  107. cloid: Optional[Cloid] = None,
  108. builder: Optional[BuilderInfo] = None,
  109. ) -> Any:
  110. order: OrderRequest = {
  111. "coin": name,
  112. "is_buy": is_buy,
  113. "sz": sz,
  114. "limit_px": limit_px,
  115. "order_type": order_type,
  116. "reduce_only": reduce_only,
  117. }
  118. if cloid:
  119. order["cloid"] = cloid
  120. return self.bulk_orders([order], builder)
  121. def bulk_orders(self, order_requests: List[OrderRequest], builder: Optional[BuilderInfo] = None) -> Any:
  122. order_wires: List[OrderWire] = [
  123. order_request_to_order_wire(order, self.info.name_to_asset(order["coin"])) for order in order_requests
  124. ]
  125. timestamp = get_timestamp_ms()
  126. if builder:
  127. builder["b"] = builder["b"].lower()
  128. order_action = order_wires_to_order_action(order_wires, builder)
  129. signature = sign_l1_action(
  130. self.wallet,
  131. order_action,
  132. self.vault_address,
  133. timestamp,
  134. self.expires_after,
  135. self.base_url == MAINNET_API_URL,
  136. )
  137. return self._post_action(
  138. order_action,
  139. signature,
  140. timestamp,
  141. )
  142. def modify_order(
  143. self,
  144. oid: OidOrCloid,
  145. name: str,
  146. is_buy: bool,
  147. sz: float,
  148. limit_px: float,
  149. order_type: OrderType,
  150. reduce_only: bool = False,
  151. cloid: Optional[Cloid] = None,
  152. ) -> Any:
  153. modify: ModifyRequest = {
  154. "oid": oid,
  155. "order": {
  156. "coin": name,
  157. "is_buy": is_buy,
  158. "sz": sz,
  159. "limit_px": limit_px,
  160. "order_type": order_type,
  161. "reduce_only": reduce_only,
  162. "cloid": cloid,
  163. },
  164. }
  165. return self.bulk_modify_orders_new([modify])
  166. def bulk_modify_orders_new(self, modify_requests: List[ModifyRequest]) -> Any:
  167. timestamp = get_timestamp_ms()
  168. modify_wires = [
  169. {
  170. "oid": modify["oid"].to_raw() if isinstance(modify["oid"], Cloid) else modify["oid"],
  171. "order": order_request_to_order_wire(modify["order"], self.info.name_to_asset(modify["order"]["coin"])),
  172. }
  173. for modify in modify_requests
  174. ]
  175. modify_action = {
  176. "type": "batchModify",
  177. "modifies": modify_wires,
  178. }
  179. signature = sign_l1_action(
  180. self.wallet,
  181. modify_action,
  182. self.vault_address,
  183. timestamp,
  184. self.expires_after,
  185. self.base_url == MAINNET_API_URL,
  186. )
  187. return self._post_action(
  188. modify_action,
  189. signature,
  190. timestamp,
  191. )
  192. def market_open(
  193. self,
  194. name: str,
  195. is_buy: bool,
  196. sz: float,
  197. px: Optional[float] = None,
  198. slippage: float = DEFAULT_SLIPPAGE,
  199. cloid: Optional[Cloid] = None,
  200. builder: Optional[BuilderInfo] = None,
  201. ) -> Any:
  202. # Get aggressive Market Price
  203. px = self._slippage_price(name, is_buy, slippage, px)
  204. # Market Order is an aggressive Limit Order IoC
  205. return self.order(
  206. name, is_buy, sz, px, order_type={"limit": {"tif": "Ioc"}}, reduce_only=False, cloid=cloid, builder=builder
  207. )
  208. def market_close(
  209. self,
  210. coin: str,
  211. sz: Optional[float] = None,
  212. px: Optional[float] = None,
  213. slippage: float = DEFAULT_SLIPPAGE,
  214. cloid: Optional[Cloid] = None,
  215. builder: Optional[BuilderInfo] = None,
  216. ) -> Any:
  217. address: str = self.wallet.address
  218. if self.account_address:
  219. address = self.account_address
  220. if self.vault_address:
  221. address = self.vault_address
  222. positions = self.info.user_state(address)["assetPositions"]
  223. for position in positions:
  224. item = position["position"]
  225. if coin != item["coin"]:
  226. continue
  227. szi = float(item["szi"])
  228. if not sz:
  229. sz = abs(szi)
  230. is_buy = True if szi < 0 else False
  231. # Get aggressive Market Price
  232. px = self._slippage_price(coin, is_buy, slippage, px)
  233. # Market Order is an aggressive Limit Order IoC
  234. return self.order(
  235. coin,
  236. is_buy,
  237. sz,
  238. px,
  239. order_type={"limit": {"tif": "Ioc"}},
  240. reduce_only=True,
  241. cloid=cloid,
  242. builder=builder,
  243. )
  244. def cancel(self, name: str, oid: int) -> Any:
  245. return self.bulk_cancel([{"coin": name, "oid": oid}])
  246. def cancel_by_cloid(self, name: str, cloid: Cloid) -> Any:
  247. return self.bulk_cancel_by_cloid([{"coin": name, "cloid": cloid}])
  248. def bulk_cancel(self, cancel_requests: List[CancelRequest]) -> Any:
  249. timestamp = get_timestamp_ms()
  250. cancel_action = {
  251. "type": "cancel",
  252. "cancels": [
  253. {
  254. "a": self.info.name_to_asset(cancel["coin"]),
  255. "o": cancel["oid"],
  256. }
  257. for cancel in cancel_requests
  258. ],
  259. }
  260. signature = sign_l1_action(
  261. self.wallet,
  262. cancel_action,
  263. self.vault_address,
  264. timestamp,
  265. self.expires_after,
  266. self.base_url == MAINNET_API_URL,
  267. )
  268. return self._post_action(
  269. cancel_action,
  270. signature,
  271. timestamp,
  272. )
  273. def bulk_cancel_by_cloid(self, cancel_requests: List[CancelByCloidRequest]) -> Any:
  274. timestamp = get_timestamp_ms()
  275. cancel_action = {
  276. "type": "cancelByCloid",
  277. "cancels": [
  278. {
  279. "asset": self.info.name_to_asset(cancel["coin"]),
  280. "cloid": cancel["cloid"].to_raw(),
  281. }
  282. for cancel in cancel_requests
  283. ],
  284. }
  285. signature = sign_l1_action(
  286. self.wallet,
  287. cancel_action,
  288. self.vault_address,
  289. timestamp,
  290. self.expires_after,
  291. self.base_url == MAINNET_API_URL,
  292. )
  293. return self._post_action(
  294. cancel_action,
  295. signature,
  296. timestamp,
  297. )
  298. def schedule_cancel(self, time: Optional[int]) -> Any:
  299. """Schedules a time (in UTC millis) to cancel all open orders. The time must be at least 5 seconds after the current time.
  300. Once the time comes, all open orders will be canceled and a trigger count will be incremented. The max number of triggers
  301. per day is 10. This trigger count is reset at 00:00 UTC.
  302. Args:
  303. time (int): if time is not None, then set the cancel time in the future. If None, then unsets any cancel time in the future.
  304. """
  305. timestamp = get_timestamp_ms()
  306. schedule_cancel_action: ScheduleCancelAction = {
  307. "type": "scheduleCancel",
  308. }
  309. if time is not None:
  310. schedule_cancel_action["time"] = time
  311. signature = sign_l1_action(
  312. self.wallet,
  313. schedule_cancel_action,
  314. self.vault_address,
  315. timestamp,
  316. self.expires_after,
  317. self.base_url == MAINNET_API_URL,
  318. )
  319. return self._post_action(
  320. schedule_cancel_action,
  321. signature,
  322. timestamp,
  323. )
  324. def update_leverage(self, leverage: int, name: str, is_cross: bool = True) -> Any:
  325. timestamp = get_timestamp_ms()
  326. update_leverage_action = {
  327. "type": "updateLeverage",
  328. "asset": self.info.name_to_asset(name),
  329. "isCross": is_cross,
  330. "leverage": leverage,
  331. }
  332. signature = sign_l1_action(
  333. self.wallet,
  334. update_leverage_action,
  335. self.vault_address,
  336. timestamp,
  337. self.expires_after,
  338. self.base_url == MAINNET_API_URL,
  339. )
  340. return self._post_action(
  341. update_leverage_action,
  342. signature,
  343. timestamp,
  344. )
  345. def update_isolated_margin(self, amount: float, name: str) -> Any:
  346. timestamp = get_timestamp_ms()
  347. amount = float_to_usd_int(amount)
  348. update_isolated_margin_action = {
  349. "type": "updateIsolatedMargin",
  350. "asset": self.info.name_to_asset(name),
  351. "isBuy": True,
  352. "ntli": amount,
  353. }
  354. signature = sign_l1_action(
  355. self.wallet,
  356. update_isolated_margin_action,
  357. self.vault_address,
  358. timestamp,
  359. self.expires_after,
  360. self.base_url == MAINNET_API_URL,
  361. )
  362. return self._post_action(
  363. update_isolated_margin_action,
  364. signature,
  365. timestamp,
  366. )
  367. def set_referrer(self, code: str) -> Any:
  368. timestamp = get_timestamp_ms()
  369. set_referrer_action = {
  370. "type": "setReferrer",
  371. "code": code,
  372. }
  373. signature = sign_l1_action(
  374. self.wallet,
  375. set_referrer_action,
  376. None,
  377. timestamp,
  378. self.expires_after,
  379. self.base_url == MAINNET_API_URL,
  380. )
  381. return self._post_action(
  382. set_referrer_action,
  383. signature,
  384. timestamp,
  385. )
  386. def create_sub_account(self, name: str) -> Any:
  387. timestamp = get_timestamp_ms()
  388. create_sub_account_action = {
  389. "type": "createSubAccount",
  390. "name": name,
  391. }
  392. signature = sign_l1_action(
  393. self.wallet,
  394. create_sub_account_action,
  395. None,
  396. timestamp,
  397. self.expires_after,
  398. self.base_url == MAINNET_API_URL,
  399. )
  400. return self._post_action(
  401. create_sub_account_action,
  402. signature,
  403. timestamp,
  404. )
  405. def usd_class_transfer(self, amount: float, to_perp: bool) -> Any:
  406. timestamp = get_timestamp_ms()
  407. str_amount = str(amount)
  408. if self.vault_address:
  409. str_amount += f" subaccount:{self.vault_address}"
  410. action = {
  411. "type": "usdClassTransfer",
  412. "amount": str_amount,
  413. "toPerp": to_perp,
  414. "nonce": timestamp,
  415. }
  416. signature = sign_usd_class_transfer_action(self.wallet, action, self.base_url == MAINNET_API_URL)
  417. return self._post_action(
  418. action,
  419. signature,
  420. timestamp,
  421. )
  422. def send_asset(self, destination: str, source_dex: str, destination_dex: str, token: str, amount: float) -> Any:
  423. """
  424. For the default perp dex use the empty string "" as name. For spot use "spot".
  425. Token must match the collateral token if transferring to or from a perp dex.
  426. """
  427. timestamp = get_timestamp_ms()
  428. str_amount = str(amount)
  429. action = {
  430. "type": "sendAsset",
  431. "destination": destination,
  432. "sourceDex": source_dex,
  433. "destinationDex": destination_dex,
  434. "token": token,
  435. "amount": str_amount,
  436. "fromSubAccount": self.vault_address if self.vault_address else "",
  437. "nonce": timestamp,
  438. }
  439. signature = sign_send_asset_action(self.wallet, action, self.base_url == MAINNET_API_URL)
  440. return self._post_action(
  441. action,
  442. signature,
  443. timestamp,
  444. )
  445. def sub_account_transfer(self, sub_account_user: str, is_deposit: bool, usd: int) -> Any:
  446. timestamp = get_timestamp_ms()
  447. sub_account_transfer_action = {
  448. "type": "subAccountTransfer",
  449. "subAccountUser": sub_account_user,
  450. "isDeposit": is_deposit,
  451. "usd": usd,
  452. }
  453. signature = sign_l1_action(
  454. self.wallet,
  455. sub_account_transfer_action,
  456. None,
  457. timestamp,
  458. self.expires_after,
  459. self.base_url == MAINNET_API_URL,
  460. )
  461. return self._post_action(
  462. sub_account_transfer_action,
  463. signature,
  464. timestamp,
  465. )
  466. def sub_account_spot_transfer(self, sub_account_user: str, is_deposit: bool, token: str, amount: float) -> Any:
  467. timestamp = get_timestamp_ms()
  468. sub_account_transfer_action = {
  469. "type": "subAccountSpotTransfer",
  470. "subAccountUser": sub_account_user,
  471. "isDeposit": is_deposit,
  472. "token": token,
  473. "amount": str(amount),
  474. }
  475. signature = sign_l1_action(
  476. self.wallet,
  477. sub_account_transfer_action,
  478. None,
  479. timestamp,
  480. self.expires_after,
  481. self.base_url == MAINNET_API_URL,
  482. )
  483. return self._post_action(
  484. sub_account_transfer_action,
  485. signature,
  486. timestamp,
  487. )
  488. def vault_usd_transfer(self, vault_address: str, is_deposit: bool, usd: int) -> Any:
  489. timestamp = get_timestamp_ms()
  490. vault_transfer_action = {
  491. "type": "vaultTransfer",
  492. "vaultAddress": vault_address,
  493. "isDeposit": is_deposit,
  494. "usd": usd,
  495. }
  496. is_mainnet = self.base_url == MAINNET_API_URL
  497. signature = sign_l1_action(self.wallet, vault_transfer_action, None, timestamp, self.expires_after, is_mainnet)
  498. return self._post_action(
  499. vault_transfer_action,
  500. signature,
  501. timestamp,
  502. )
  503. def usd_transfer(self, amount: float, destination: str) -> Any:
  504. timestamp = get_timestamp_ms()
  505. action = {"destination": destination, "amount": str(amount), "time": timestamp, "type": "usdSend"}
  506. is_mainnet = self.base_url == MAINNET_API_URL
  507. signature = sign_usd_transfer_action(self.wallet, action, is_mainnet)
  508. return self._post_action(
  509. action,
  510. signature,
  511. timestamp,
  512. )
  513. def spot_transfer(self, amount: float, destination: str, token: str) -> Any:
  514. timestamp = get_timestamp_ms()
  515. action = {
  516. "destination": destination,
  517. "amount": str(amount),
  518. "token": token,
  519. "time": timestamp,
  520. "type": "spotSend",
  521. }
  522. is_mainnet = self.base_url == MAINNET_API_URL
  523. signature = sign_spot_transfer_action(self.wallet, action, is_mainnet)
  524. return self._post_action(
  525. action,
  526. signature,
  527. timestamp,
  528. )
  529. def token_delegate(self, validator: str, wei: int, is_undelegate: bool) -> Any:
  530. timestamp = get_timestamp_ms()
  531. action = {
  532. "validator": validator,
  533. "wei": wei,
  534. "isUndelegate": is_undelegate,
  535. "nonce": timestamp,
  536. "type": "tokenDelegate",
  537. }
  538. is_mainnet = self.base_url == MAINNET_API_URL
  539. signature = sign_token_delegate_action(self.wallet, action, is_mainnet)
  540. return self._post_action(
  541. action,
  542. signature,
  543. timestamp,
  544. )
  545. def withdraw_from_bridge(self, amount: float, destination: str) -> Any:
  546. timestamp = get_timestamp_ms()
  547. action = {"destination": destination, "amount": str(amount), "time": timestamp, "type": "withdraw3"}
  548. is_mainnet = self.base_url == MAINNET_API_URL
  549. signature = sign_withdraw_from_bridge_action(self.wallet, action, is_mainnet)
  550. return self._post_action(
  551. action,
  552. signature,
  553. timestamp,
  554. )
  555. def approve_agent(self, name: Optional[str] = None) -> Tuple[Any, str]:
  556. agent_key = "0x" + secrets.token_hex(32)
  557. account = eth_account.Account.from_key(agent_key)
  558. timestamp = get_timestamp_ms()
  559. is_mainnet = self.base_url == MAINNET_API_URL
  560. action = {
  561. "type": "approveAgent",
  562. "agentAddress": account.address,
  563. "agentName": name or "",
  564. "nonce": timestamp,
  565. }
  566. signature = sign_agent(self.wallet, action, is_mainnet)
  567. if name is None:
  568. del action["agentName"]
  569. return (
  570. self._post_action(
  571. action,
  572. signature,
  573. timestamp,
  574. ),
  575. agent_key,
  576. )
  577. def approve_builder_fee(self, builder: str, max_fee_rate: str) -> Any:
  578. timestamp = get_timestamp_ms()
  579. action = {"maxFeeRate": max_fee_rate, "builder": builder, "nonce": timestamp, "type": "approveBuilderFee"}
  580. signature = sign_approve_builder_fee(self.wallet, action, self.base_url == MAINNET_API_URL)
  581. return self._post_action(action, signature, timestamp)
  582. def convert_to_multi_sig_user(self, authorized_users: List[str], threshold: int) -> Any:
  583. timestamp = get_timestamp_ms()
  584. authorized_users = sorted(authorized_users)
  585. signers = {
  586. "authorizedUsers": authorized_users,
  587. "threshold": threshold,
  588. }
  589. action = {
  590. "type": "convertToMultiSigUser",
  591. "signers": json.dumps(signers),
  592. "nonce": timestamp,
  593. }
  594. signature = sign_convert_to_multi_sig_user_action(self.wallet, action, self.base_url == MAINNET_API_URL)
  595. return self._post_action(
  596. action,
  597. signature,
  598. timestamp,
  599. )
  600. def spot_deploy_register_token(
  601. self, token_name: str, sz_decimals: int, wei_decimals: int, max_gas: int, full_name: str
  602. ) -> Any:
  603. timestamp = get_timestamp_ms()
  604. action = {
  605. "type": "spotDeploy",
  606. "registerToken2": {
  607. "spec": {"name": token_name, "szDecimals": sz_decimals, "weiDecimals": wei_decimals},
  608. "maxGas": max_gas,
  609. "fullName": full_name,
  610. },
  611. }
  612. signature = sign_l1_action(
  613. self.wallet,
  614. action,
  615. None,
  616. timestamp,
  617. self.expires_after,
  618. self.base_url == MAINNET_API_URL,
  619. )
  620. return self._post_action(
  621. action,
  622. signature,
  623. timestamp,
  624. )
  625. def spot_deploy_user_genesis(
  626. self, token: int, user_and_wei: List[Tuple[str, str]], existing_token_and_wei: List[Tuple[int, str]]
  627. ) -> Any:
  628. timestamp = get_timestamp_ms()
  629. action = {
  630. "type": "spotDeploy",
  631. "userGenesis": {
  632. "token": token,
  633. "userAndWei": [(user.lower(), wei) for (user, wei) in user_and_wei],
  634. "existingTokenAndWei": existing_token_and_wei,
  635. },
  636. }
  637. signature = sign_l1_action(
  638. self.wallet,
  639. action,
  640. None,
  641. timestamp,
  642. self.expires_after,
  643. self.base_url == MAINNET_API_URL,
  644. )
  645. return self._post_action(
  646. action,
  647. signature,
  648. timestamp,
  649. )
  650. def spot_deploy_enable_freeze_privilege(self, token: int) -> Any:
  651. return self.spot_deploy_token_action_inner("enableFreezePrivilege", token)
  652. def spot_deploy_freeze_user(self, token: int, user: str, freeze: bool) -> Any:
  653. timestamp = get_timestamp_ms()
  654. action = {
  655. "type": "spotDeploy",
  656. "freezeUser": {
  657. "token": token,
  658. "user": user.lower(),
  659. "freeze": freeze,
  660. },
  661. }
  662. signature = sign_l1_action(
  663. self.wallet,
  664. action,
  665. None,
  666. timestamp,
  667. self.expires_after,
  668. self.base_url == MAINNET_API_URL,
  669. )
  670. return self._post_action(
  671. action,
  672. signature,
  673. timestamp,
  674. )
  675. def spot_deploy_revoke_freeze_privilege(self, token: int) -> Any:
  676. return self.spot_deploy_token_action_inner("revokeFreezePrivilege", token)
  677. def spot_deploy_enable_quote_token(self, token: int) -> Any:
  678. return self.spot_deploy_token_action_inner("enableQuoteToken", token)
  679. def spot_deploy_token_action_inner(self, variant: str, token: int) -> Any:
  680. timestamp = get_timestamp_ms()
  681. action = {
  682. "type": "spotDeploy",
  683. variant: {
  684. "token": token,
  685. },
  686. }
  687. signature = sign_l1_action(
  688. self.wallet,
  689. action,
  690. None,
  691. timestamp,
  692. self.expires_after,
  693. self.base_url == MAINNET_API_URL,
  694. )
  695. return self._post_action(
  696. action,
  697. signature,
  698. timestamp,
  699. )
  700. def spot_deploy_genesis(self, token: int, max_supply: str, no_hyperliquidity: bool) -> Any:
  701. timestamp = get_timestamp_ms()
  702. genesis = {
  703. "token": token,
  704. "maxSupply": max_supply,
  705. }
  706. if no_hyperliquidity:
  707. genesis["noHyperliquidity"] = True
  708. action = {
  709. "type": "spotDeploy",
  710. "genesis": genesis,
  711. }
  712. signature = sign_l1_action(
  713. self.wallet,
  714. action,
  715. None,
  716. timestamp,
  717. self.expires_after,
  718. self.base_url == MAINNET_API_URL,
  719. )
  720. return self._post_action(
  721. action,
  722. signature,
  723. timestamp,
  724. )
  725. def spot_deploy_register_spot(self, base_token: int, quote_token: int) -> Any:
  726. timestamp = get_timestamp_ms()
  727. action = {
  728. "type": "spotDeploy",
  729. "registerSpot": {
  730. "tokens": [base_token, quote_token],
  731. },
  732. }
  733. signature = sign_l1_action(
  734. self.wallet,
  735. action,
  736. None,
  737. timestamp,
  738. self.expires_after,
  739. self.base_url == MAINNET_API_URL,
  740. )
  741. return self._post_action(
  742. action,
  743. signature,
  744. timestamp,
  745. )
  746. def spot_deploy_register_hyperliquidity(
  747. self, spot: int, start_px: float, order_sz: float, n_orders: int, n_seeded_levels: Optional[int]
  748. ) -> Any:
  749. timestamp = get_timestamp_ms()
  750. register_hyperliquidity = {
  751. "spot": spot,
  752. "startPx": str(start_px),
  753. "orderSz": str(order_sz),
  754. "nOrders": n_orders,
  755. }
  756. if n_seeded_levels is not None:
  757. register_hyperliquidity["nSeededLevels"] = n_seeded_levels
  758. action = {
  759. "type": "spotDeploy",
  760. "registerHyperliquidity": register_hyperliquidity,
  761. }
  762. signature = sign_l1_action(
  763. self.wallet,
  764. action,
  765. None,
  766. timestamp,
  767. self.expires_after,
  768. self.base_url == MAINNET_API_URL,
  769. )
  770. return self._post_action(
  771. action,
  772. signature,
  773. timestamp,
  774. )
  775. def spot_deploy_set_deployer_trading_fee_share(self, token: int, share: str) -> Any:
  776. timestamp = get_timestamp_ms()
  777. action = {
  778. "type": "spotDeploy",
  779. "setDeployerTradingFeeShare": {
  780. "token": token,
  781. "share": share,
  782. },
  783. }
  784. signature = sign_l1_action(
  785. self.wallet,
  786. action,
  787. None,
  788. timestamp,
  789. self.expires_after,
  790. self.base_url == MAINNET_API_URL,
  791. )
  792. return self._post_action(
  793. action,
  794. signature,
  795. timestamp,
  796. )
  797. def perp_deploy_register_asset(
  798. self,
  799. dex: str,
  800. max_gas: Optional[int],
  801. coin: str,
  802. sz_decimals: int,
  803. oracle_px: str,
  804. margin_table_id: int,
  805. only_isolated: bool,
  806. schema: Optional[PerpDexSchemaInput],
  807. ) -> Any:
  808. timestamp = get_timestamp_ms()
  809. schema_wire = None
  810. if schema is not None:
  811. schema_wire = {
  812. "fullName": schema["fullName"],
  813. "collateralToken": schema["collateralToken"],
  814. "oracleUpdater": schema["oracleUpdater"].lower() if schema["oracleUpdater"] is not None else None,
  815. }
  816. action = {
  817. "type": "perpDeploy",
  818. "registerAsset": {
  819. "maxGas": max_gas,
  820. "assetRequest": {
  821. "coin": coin,
  822. "szDecimals": sz_decimals,
  823. "oraclePx": oracle_px,
  824. "marginTableId": margin_table_id,
  825. "onlyIsolated": only_isolated,
  826. },
  827. "dex": dex,
  828. "schema": schema_wire,
  829. },
  830. }
  831. signature = sign_l1_action(
  832. self.wallet,
  833. action,
  834. None,
  835. timestamp,
  836. self.expires_after,
  837. self.base_url == MAINNET_API_URL,
  838. )
  839. return self._post_action(
  840. action,
  841. signature,
  842. timestamp,
  843. )
  844. def perp_deploy_set_oracle(
  845. self,
  846. dex: str,
  847. oracle_pxs: Dict[str, str],
  848. all_mark_pxs: List[Dict[str, str]],
  849. external_perp_pxs: Dict[str, str],
  850. ) -> Any:
  851. timestamp = get_timestamp_ms()
  852. oracle_pxs_wire = sorted(list(oracle_pxs.items()))
  853. mark_pxs_wire = [sorted(list(mark_pxs.items())) for mark_pxs in all_mark_pxs]
  854. external_perp_pxs_wire = sorted(list(external_perp_pxs.items()))
  855. action = {
  856. "type": "perpDeploy",
  857. "setOracle": {
  858. "dex": dex,
  859. "oraclePxs": oracle_pxs_wire,
  860. "markPxs": mark_pxs_wire,
  861. "externalPerpPxs": external_perp_pxs_wire,
  862. },
  863. }
  864. signature = sign_l1_action(
  865. self.wallet,
  866. action,
  867. None,
  868. timestamp,
  869. self.expires_after,
  870. self.base_url == MAINNET_API_URL,
  871. )
  872. return self._post_action(
  873. action,
  874. signature,
  875. timestamp,
  876. )
  877. def c_signer_unjail_self(self) -> Any:
  878. return self.c_signer_inner("unjailSelf")
  879. def c_signer_jail_self(self) -> Any:
  880. return self.c_signer_inner("jailSelf")
  881. def c_signer_inner(self, variant: str) -> Any:
  882. timestamp = get_timestamp_ms()
  883. action = {
  884. "type": "CSignerAction",
  885. variant: None,
  886. }
  887. signature = sign_l1_action(
  888. self.wallet,
  889. action,
  890. None,
  891. timestamp,
  892. self.expires_after,
  893. self.base_url == MAINNET_API_URL,
  894. )
  895. return self._post_action(
  896. action,
  897. signature,
  898. timestamp,
  899. )
  900. def c_validator_register(
  901. self,
  902. node_ip: str,
  903. name: str,
  904. description: str,
  905. delegations_disabled: bool,
  906. commission_bps: int,
  907. signer: str,
  908. unjailed: bool,
  909. initial_wei: int,
  910. ) -> Any:
  911. timestamp = get_timestamp_ms()
  912. action = {
  913. "type": "CValidatorAction",
  914. "register": {
  915. "profile": {
  916. "node_ip": {"Ip": node_ip},
  917. "name": name,
  918. "description": description,
  919. "delegations_disabled": delegations_disabled,
  920. "commission_bps": commission_bps,
  921. "signer": signer,
  922. },
  923. "unjailed": unjailed,
  924. "initial_wei": initial_wei,
  925. },
  926. }
  927. signature = sign_l1_action(
  928. self.wallet,
  929. action,
  930. None,
  931. timestamp,
  932. self.expires_after,
  933. self.base_url == MAINNET_API_URL,
  934. )
  935. return self._post_action(
  936. action,
  937. signature,
  938. timestamp,
  939. )
  940. def c_validator_change_profile(
  941. self,
  942. node_ip: Optional[str],
  943. name: Optional[str],
  944. description: Optional[str],
  945. unjailed: bool,
  946. disable_delegations: Optional[bool],
  947. commission_bps: Optional[int],
  948. signer: Optional[str],
  949. ) -> Any:
  950. timestamp = get_timestamp_ms()
  951. action = {
  952. "type": "CValidatorAction",
  953. "changeProfile": {
  954. "node_ip": None if node_ip is None else {"Ip": node_ip},
  955. "name": name,
  956. "description": description,
  957. "unjailed": unjailed,
  958. "disable_delegations": disable_delegations,
  959. "commission_bps": commission_bps,
  960. "signer": signer,
  961. },
  962. }
  963. signature = sign_l1_action(
  964. self.wallet,
  965. action,
  966. None,
  967. timestamp,
  968. self.expires_after,
  969. self.base_url == MAINNET_API_URL,
  970. )
  971. return self._post_action(
  972. action,
  973. signature,
  974. timestamp,
  975. )
  976. def c_validator_unregister(self) -> Any:
  977. timestamp = get_timestamp_ms()
  978. action = {
  979. "type": "CValidatorAction",
  980. "unregister": None,
  981. }
  982. signature = sign_l1_action(
  983. self.wallet,
  984. action,
  985. None,
  986. timestamp,
  987. self.expires_after,
  988. self.base_url == MAINNET_API_URL,
  989. )
  990. return self._post_action(
  991. action,
  992. signature,
  993. timestamp,
  994. )
  995. def multi_sig(self, multi_sig_user, inner_action, signatures, nonce, vault_address=None):
  996. multi_sig_user = multi_sig_user.lower()
  997. multi_sig_action = {
  998. "type": "multiSig",
  999. "signatureChainId": "0x66eee",
  1000. "signatures": signatures,
  1001. "payload": {
  1002. "multiSigUser": multi_sig_user,
  1003. "outerSigner": self.wallet.address.lower(),
  1004. "action": inner_action,
  1005. },
  1006. }
  1007. is_mainnet = self.base_url == MAINNET_API_URL
  1008. signature = sign_multi_sig_action(
  1009. self.wallet,
  1010. multi_sig_action,
  1011. is_mainnet,
  1012. vault_address,
  1013. nonce,
  1014. self.expires_after,
  1015. )
  1016. return self._post_action(
  1017. multi_sig_action,
  1018. signature,
  1019. nonce,
  1020. )
  1021. def use_big_blocks(self, enable: bool) -> Any:
  1022. timestamp = get_timestamp_ms()
  1023. action = {
  1024. "type": "evmUserModify",
  1025. "usingBigBlocks": enable,
  1026. }
  1027. signature = sign_l1_action(
  1028. self.wallet,
  1029. action,
  1030. None,
  1031. timestamp,
  1032. self.expires_after,
  1033. self.base_url == MAINNET_API_URL,
  1034. )
  1035. return self._post_action(
  1036. action,
  1037. signature,
  1038. timestamp,
  1039. )
  1040. def noop(self, nonce):
  1041. action = {"type": "noop"}
  1042. signature = sign_l1_action(
  1043. self.wallet, action, self.vault_address, nonce, self.expires_after, self.base_url == MAINNET_API_URL
  1044. )
  1045. return self._post_action(action, signature, nonce)