diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c0bc002..aa24e474 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,11 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install poetry - run: make poetry-download + uses: snok/install-poetry@v1 + with: + version: 1.8.5 + virtualenvs-create: true + virtualenvs-in-project: true - name: Set up cache uses: actions/cache@v4 @@ -42,9 +46,7 @@ jobs: key: venv-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}-${{ hashFiles('poetry.lock') }} - name: Install dependencies - run: | - poetry config virtualenvs.in-project true - poetry install + run: poetry install - name: Run tests run: | diff --git a/examples/dex_abstraction.py b/examples/dex_abstraction.py index c15f715d..338423c4 100644 --- a/examples/dex_abstraction.py +++ b/examples/dex_abstraction.py @@ -1,4 +1,5 @@ # See https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#enable-hip-3-dex-abstraction for more details +# NOTE: setting dex_abstraction is legacy. Prefer setting user_abstraction in user_abstraction.py import example_utils from hyperliquid.exchange import Exchange diff --git a/examples/user_abstraction.py b/examples/user_abstraction.py new file mode 100644 index 00000000..d14a52da --- /dev/null +++ b/examples/user_abstraction.py @@ -0,0 +1,47 @@ +import example_utils + +from hyperliquid.exchange import Exchange +from hyperliquid.utils import constants + +# set sub-account user address here +SUB_ACCOUNT_USER = "0x0000000000000000000000000000000000000000" + + +def main(): + address, info, exchange = example_utils.setup(constants.TESTNET_API_URL, skip_ws=True) + + # set abstraction for user via agent + # Note: the account must be in "default" mode to succeed + user = exchange.account_address + print("current user abstraction state:", info.query_user_abstraction_state(user)) + agent_set_abstraction_result = exchange.agent_set_abstraction("u") + print(agent_set_abstraction_result) + + if user == exchange.wallet.address: + # set abstraction for user back to disabled + user_set_abstraction_result = exchange.user_set_abstraction(user, "unifiedAccount") + print(user_set_abstraction_result) + print("current user abstraction state:", info.query_user_abstraction_state(user)) + + # set dex abstraction for sub-account of user + print("setting abstraction for", SUB_ACCOUNT_USER) + + # set abstraction for user via agent by setting the vault_address to SUB_ACCOUNT_USER + exchange_with_sub_account = Exchange(exchange.wallet, exchange.base_url, vault_address=SUB_ACCOUNT_USER) + agent_set_abstraction_result = exchange_with_sub_account.agent_set_abstraction("u") + print("sub-account agent_set_abstraction result:", agent_set_abstraction_result) + + for abstraction in ["disabled", "portfolioMargin"]: + user_set_abstraction_result = exchange.user_set_abstraction(SUB_ACCOUNT_USER, abstraction) + print(user_set_abstraction_result) + print( + "current sub-account user abstraction state:", + info.query_user_abstraction_state(SUB_ACCOUNT_USER), + ) + + else: + print("not performing user set abstraction because not user", exchange.account_address, exchange.wallet.address) + + +if __name__ == "__main__": + main() diff --git a/hyperliquid/exchange.py b/hyperliquid/exchange.py index cc539fa1..688f6405 100644 --- a/hyperliquid/exchange.py +++ b/hyperliquid/exchange.py @@ -33,9 +33,12 @@ sign_usd_class_transfer_action, sign_usd_transfer_action, sign_user_dex_abstraction_action, + sign_user_set_abstraction_action, sign_withdraw_from_bridge_action, ) from hyperliquid.utils.types import ( + Abstraction, + AgentAbstraction, Any, BuilderInfo, Cloid, @@ -1139,6 +1142,26 @@ def agent_enable_dex_abstraction(self) -> Any: timestamp, ) + def agent_set_abstraction(self, abstraction: AgentAbstraction) -> Any: + timestamp = get_timestamp_ms() + action = { + "type": "agentSetAbstraction", + "abstraction": abstraction, + } + signature = sign_l1_action( + self.wallet, + action, + self.vault_address, + timestamp, + self.expires_after, + self.base_url == MAINNET_API_URL, + ) + return self._post_action( + action, + signature, + timestamp, + ) + def user_dex_abstraction(self, user: str, enabled: bool) -> Any: timestamp = get_timestamp_ms() action = { @@ -1154,6 +1177,21 @@ def user_dex_abstraction(self, user: str, enabled: bool) -> Any: timestamp, ) + def user_set_abstraction(self, user: str, abstraction: Abstraction) -> Any: + timestamp = get_timestamp_ms() + action = { + "type": "userSetAbstraction", + "user": user.lower(), + "abstraction": abstraction, + "nonce": timestamp, + } + signature = sign_user_set_abstraction_action(self.wallet, action, self.base_url == MAINNET_API_URL) + return self._post_action( + action, + signature, + timestamp, + ) + def noop(self, nonce): action = {"type": "noop"} signature = sign_l1_action( diff --git a/hyperliquid/info.py b/hyperliquid/info.py index b59f81e8..a86ac807 100644 --- a/hyperliquid/info.py +++ b/hyperliquid/info.py @@ -629,6 +629,9 @@ def query_perp_deploy_auction_status(self) -> Any: def query_user_dex_abstraction_state(self, user: str) -> Any: return self.post("/info", {"type": "userDexAbstraction", "user": user}) + def query_user_abstraction_state(self, user: str) -> Any: + return self.post("/info", {"type": "userAbstraction", "user": user}) + def historical_orders(self, user: str) -> Any: """Retrieve a user's historical orders. diff --git a/hyperliquid/utils/signing.py b/hyperliquid/utils/signing.py index 55e8192e..10cca4d9 100644 --- a/hyperliquid/utils/signing.py +++ b/hyperliquid/utils/signing.py @@ -124,6 +124,13 @@ {"name": "nonce", "type": "uint64"}, ] +USER_SET_ABSTRACTION_SIGN_TYPES = [ + {"name": "hyperliquidChain", "type": "string"}, + {"name": "user", "type": "address"}, + {"name": "abstraction", "type": "string"}, + {"name": "nonce", "type": "uint64"}, +] + TOKEN_DELEGATE_TYPES = [ {"name": "hyperliquidChain", "type": "string"}, {"name": "validator", "type": "address"}, @@ -381,6 +388,16 @@ def sign_user_dex_abstraction_action(wallet, action, is_mainnet): ) +def sign_user_set_abstraction_action(wallet, action, is_mainnet): + return sign_user_signed_action( + wallet, + action, + USER_SET_ABSTRACTION_SIGN_TYPES, + "HyperliquidTransaction:UserSetAbstraction", + is_mainnet, + ) + + def sign_convert_to_multi_sig_user_action(wallet, action, is_mainnet): return sign_user_signed_action( wallet, diff --git a/hyperliquid/utils/types.py b/hyperliquid/utils/types.py index 051bcfa6..14dc67bd 100644 --- a/hyperliquid/utils/types.py +++ b/hyperliquid/utils/types.py @@ -183,6 +183,8 @@ # b is the public address of the builder, f is the amount of the fee in tenths of basis points. e.g. 10 means 1 basis point BuilderInfo = TypedDict("BuilderInfo", {"b": str, "f": int}) +Abstraction = Literal["unifiedAccount", "portfolioMargin", "disabled"] +AgentAbstraction = Literal["u", "p", "i"] PerpDexSchemaInput = TypedDict( "PerpDexSchemaInput", {"fullName": str, "collateralToken": int, "oracleUpdater": Optional[str]} diff --git a/pyproject.toml b/pyproject.toml index db31aca2..a7495906 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "hyperliquid-python-sdk" -version = "0.21.0" +version = "0.22.0" description = "SDK for Hyperliquid API trading with Python." readme = "README.md" authors = ["Hyperliquid "]