From cf6d7083351dd66c3ae50d3e3a7474ee88588f53 Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:06:58 -0800
Subject: [PATCH 01/16] Add tests for exchange initializer
---
assets/images/coverage.svg | 4 ++--
tests/test_exchange.py | 49 ++++++++++++++++++++++++++++++++++++++
2 files changed, 51 insertions(+), 2 deletions(-)
create mode 100644 tests/test_exchange.py
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index 5cc1bb5..4f8c185 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 42%
- 42%
+ 50%
+ 50%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
new file mode 100644
index 0000000..3bae519
--- /dev/null
+++ b/tests/test_exchange.py
@@ -0,0 +1,49 @@
+import pytest
+from eth_account import Account
+from eth_account.signers.local import LocalAccount
+
+from hyperliquid.exchange import Exchange
+from hyperliquid.utils.constants import MAINNET_API_URL
+from hyperliquid.utils.types import Meta, SpotMeta
+
+TEST_META: Meta = {"universe": []}
+TEST_SPOT_META: SpotMeta = {"universe": [], "tokens": []}
+
+@pytest.fixture
+def wallet() -> LocalAccount:
+ """Create a test wallet"""
+ return Account.from_key("0x0123456789012345678901234567890123456789012345678901234567890123")
+
+@pytest.fixture
+def exchange(wallet):
+ """Fixture that provides an Exchange instance"""
+ return Exchange(wallet)
+
+def test_initializer(exchange, wallet):
+ """Test that the Exchange class initializes with correct default values"""
+ assert exchange.base_url == MAINNET_API_URL
+ assert exchange.wallet == wallet
+ assert exchange.vault_address is None
+ assert exchange.account_address is None
+ assert exchange.info is not None
+
+def test_initializer_with_custom_values(wallet):
+ """Test that the Exchange class can be initialized with custom values"""
+ custom_url = "https://custom.api.url"
+ vault_address = "0x1234567890123456789012345678901234567890"
+ account_address = "0x0987654321098765432109876543210987654321"
+
+ exchange = Exchange(
+ wallet=wallet,
+ base_url=custom_url,
+ meta=TEST_META,
+ vault_address=vault_address,
+ account_address=account_address,
+ spot_meta=TEST_SPOT_META
+ )
+
+ assert exchange.base_url == custom_url
+ assert exchange.wallet == wallet
+ assert exchange.vault_address == vault_address
+ assert exchange.account_address == account_address
+ assert exchange.info is not None
From 7b0e47540b8053546f8b91683fc3dd8857c7b193 Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:12:36 -0800
Subject: [PATCH 02/16] Add tests for post and slippage price
---
assets/images/coverage.svg | 4 +-
tests/test_exchange.py | 108 +++++++++++++++++++++++++++++++++++++
2 files changed, 110 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index 4f8c185..d4eaeaa 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 50%
- 50%
+ 52%
+ 52%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index 3bae519..322b4fc 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -1,6 +1,7 @@
import pytest
from eth_account import Account
from eth_account.signers.local import LocalAccount
+from unittest.mock import Mock, patch
from hyperliquid.exchange import Exchange
from hyperliquid.utils.constants import MAINNET_API_URL
@@ -47,3 +48,110 @@ def test_initializer_with_custom_values(wallet):
assert exchange.vault_address == vault_address
assert exchange.account_address == account_address
assert exchange.info is not None
+
+@patch('hyperliquid.api.API.post')
+def test_post_action(mock_post, exchange):
+ """Test _post_action method"""
+ # Setup
+ mock_post.return_value = {"status": "ok"}
+ action = {"type": "someAction", "data": "test"}
+ signature = "test_signature"
+ nonce = 123456789
+
+ # Test with regular action
+ response = exchange._post_action(action, signature, nonce)
+ mock_post.assert_called_once_with(
+ "/exchange",
+ {
+ "action": action,
+ "nonce": nonce,
+ "signature": signature,
+ "vaultAddress": None
+ }
+ )
+ assert response == {"status": "ok"}
+
+ # Test with vault address
+ mock_post.reset_mock()
+ exchange.vault_address = "0x1234"
+ response = exchange._post_action(action, signature, nonce)
+ mock_post.assert_called_once_with(
+ "/exchange",
+ {
+ "action": action,
+ "nonce": nonce,
+ "signature": signature,
+ "vaultAddress": "0x1234"
+ }
+ )
+
+ # Test with usdClassTransfer action
+ mock_post.reset_mock()
+ action["type"] = "usdClassTransfer"
+ response = exchange._post_action(action, signature, nonce)
+ mock_post.assert_called_once_with(
+ "/exchange",
+ {
+ "action": action,
+ "nonce": nonce,
+ "signature": signature,
+ "vaultAddress": None
+ }
+ )
+
+@patch('hyperliquid.info.Info.all_mids')
+def test_slippage_price_perp(mock_all_mids, exchange):
+ """Test _slippage_price method for perpetual contracts"""
+ # Setup
+ mock_all_mids.return_value = {"ETH": "2000.0"}
+ exchange.info.name_to_coin = {"ETH": "ETH"}
+ exchange.info.coin_to_asset = {"ETH": 1} # Asset ID less than 10000 for perp
+
+ # Test buy with default price
+ price = exchange._slippage_price("ETH", True, 0.05) # 5% slippage
+ assert price == 2100.0 # 2000 * (1 + 0.05)
+
+ # Test sell with default price
+ price = exchange._slippage_price("ETH", False, 0.05)
+ assert price == 1900.0 # 2000 * (1 - 0.05)
+
+ # Test with custom price
+ price = exchange._slippage_price("ETH", True, 0.05, 1000.0)
+ assert price == 1050.0 # 1000 * (1 + 0.05)
+
+@patch('hyperliquid.info.Info.all_mids')
+def test_slippage_price_spot(mock_all_mids, exchange):
+ """Test _slippage_price method for spot trading"""
+ # Setup
+ mock_all_mids.return_value = {"BTC/USDC": "40000.0"}
+ exchange.info.name_to_coin = {"BTC/USDC": "BTC/USDC"}
+ exchange.info.coin_to_asset = {"BTC/USDC": 10001} # Asset ID >= 10000 for spot
+
+ # Test buy with default price
+ price = exchange._slippage_price("BTC/USDC", True, 0.05)
+ assert price == 42000.0 # 40000 * (1 + 0.05)
+
+ # Test sell with default price
+ price = exchange._slippage_price("BTC/USDC", False, 0.05)
+ assert price == 38000.0 # 40000 * (1 - 0.05)
+
+@patch('hyperliquid.info.Info.all_mids')
+def test_slippage_price_rounding(mock_all_mids, exchange):
+ """Test price rounding in _slippage_price method"""
+ # Setup for perp
+ mock_all_mids.return_value = {"ETH": "1999.123456789"}
+ exchange.info.name_to_coin = {"ETH": "ETH"}
+ exchange.info.coin_to_asset = {"ETH": 1}
+
+ # Test perp rounding (6 decimals)
+ price = exchange._slippage_price("ETH", True, 0.05)
+ assert str(price).count('.') == 0 or len(str(price).split('.')[1]) <= 6
+
+ # Setup for spot
+ mock_all_mids.return_value = {"BTC/USDC": "40000.123456789"}
+ exchange.info.name_to_coin = {"BTC/USDC": "BTC/USDC"}
+ exchange.info.coin_to_asset = {"BTC/USDC": 10001}
+
+ # Test spot rounding (8 decimals)
+ price = exchange._slippage_price("BTC/USDC", True, 0.05)
+ assert str(price).count('.') == 0 or len(str(price).split('.')[1]) <= 8
From fc030adf06223eef5b5bf1673344587061c74928 Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:22:49 -0800
Subject: [PATCH 03/16] Add tests for orders
---
assets/images/coverage.svg | 4 +-
tests/test_exchange.py | 154 +++++++++++++++++++++++++++++++++++++
2 files changed, 156 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index d4eaeaa..d1f8965 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 52%
- 52%
+ 53%
+ 53%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index 322b4fc..e952540 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -155,3 +155,157 @@ def test_slippage_price_rounding(mock_all_mids, exchange):
# Test spot rounding (8 decimals)
price = exchange._slippage_price("BTC/USDC", True, 0.05)
assert str(price).count('.') == 0 or len(str(price).split('.')[1]) <= 8
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_bulk_orders(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test bulk_orders method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+ exchange.info.name_to_asset = lambda x: 1 # Mock name_to_asset to return 1 for any input
+
+ # Test single order
+ order_requests = [
+ {
+ "coin": "ETH",
+ "is_buy": True,
+ "sz": 1.0,
+ "limit_px": 2000.0,
+ "order_type": {"limit": {"tif": "Gtc"}},
+ "reduce_only": False,
+ }
+ ]
+
+ response = exchange.bulk_orders(order_requests)
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ assert call_args[0] == exchange.wallet # wallet
+ assert call_args[1]["type"] == "order" # action
+ assert call_args[2] == exchange.vault_address # vault_address
+ assert call_args[3] == 1234567890 # timestamp
+ assert call_args[4] == (exchange.base_url == MAINNET_API_URL) # is_mainnet
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once_with(
+ mock_sign.call_args[0][1], # action
+ "test_signature", # signature
+ 1234567890 # timestamp
+ )
+
+ # Test with builder
+ mock_sign.reset_mock()
+ mock_post_action.reset_mock()
+
+ builder = {"b": "TEST_BUILDER", "r": 0.001} # Using uppercase to test lowercasing
+ response = exchange.bulk_orders(order_requests, builder)
+
+ assert response == {"status": "ok"}
+
+ # Verify builder was included in the action
+ call_args = mock_sign.call_args[0]
+ action = call_args[1] # Get the action argument
+ assert action["type"] == "order"
+ # The builder object should be passed through as is (after lowercase conversion)
+ assert action["builder"]["b"] == "test_builder"
+ assert action["builder"]["r"] == 0.001
+
+@patch('hyperliquid.exchange.Exchange.bulk_orders')
+def test_order(mock_bulk_orders, exchange):
+ """Test order method with various scenarios"""
+ # Setup
+ mock_bulk_orders.return_value = {
+ "status": "ok",
+ "response": {
+ "data": {
+ "statuses": [{"resting": {"oid": 123}}]
+ }
+ }
+ }
+ exchange.info.name_to_asset = lambda x: 1
+
+ # Test 1: Basic limit order
+ response = exchange.order(
+ name="ETH",
+ is_buy=True,
+ sz=1.0,
+ limit_px=2000.0,
+ order_type={"limit": {"tif": "Gtc"}},
+ )
+
+ assert response["status"] == "ok"
+ assert response["response"]["data"]["statuses"][0]["resting"]["oid"] == 123
+
+ mock_bulk_orders.assert_called_once_with(
+ [
+ {
+ "coin": "ETH",
+ "is_buy": True,
+ "sz": 1.0,
+ "limit_px": 2000.0,
+ "order_type": {"limit": {"tif": "Gtc"}},
+ "reduce_only": False,
+ }
+ ],
+ None
+ )
+
+ # Test 2: Order with builder fee
+ mock_bulk_orders.reset_mock()
+ builder = {"b": "0x8c967E73E7B15087c42A10D344cFf4c96D877f1D", "r": 0.001}
+
+ response = exchange.order(
+ name="ETH",
+ is_buy=True,
+ sz=0.05,
+ limit_px=2000.0,
+ order_type={"limit": {"tif": "Ioc"}},
+ builder=builder
+ )
+
+ assert response["status"] == "ok"
+ mock_bulk_orders.assert_called_once()
+ call_args = mock_bulk_orders.call_args[0]
+ assert call_args[1]["b"].lower() == builder["b"].lower()
+ assert call_args[1]["r"] == builder["r"]
+
+ # Test 3: TPSL order
+ mock_bulk_orders.reset_mock()
+ response = exchange.order(
+ name="ETH",
+ is_buy=True,
+ sz=100,
+ limit_px=100,
+ order_type={"trigger": {"triggerPx": 103, "isMarket": True, "tpsl": "sl"}},
+ reduce_only=True
+ )
+
+ assert response["status"] == "ok"
+ mock_bulk_orders.assert_called_once()
+ order_request = mock_bulk_orders.call_args[0][0][0]
+ assert order_request["order_type"]["trigger"]["tpsl"] == "sl"
+ assert order_request["reduce_only"] is True
+
+ # Test 4: Order with cloid
+ mock_bulk_orders.reset_mock()
+ cloid = "0x00000000000000000000000000000001"
+
+ response = exchange.order(
+ name="ETH",
+ is_buy=True,
+ sz=1.0,
+ limit_px=2000.0,
+ order_type={"limit": {"tif": "Gtc"}},
+ cloid=cloid
+ )
+
+ assert response["status"] == "ok"
+ mock_bulk_orders.assert_called_once()
+ order_request = mock_bulk_orders.call_args[0][0][0]
+ assert order_request["cloid"] == cloid
From 95dddb2de7e03d6936733533d7c2db183f7faa9c Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:28:19 -0800
Subject: [PATCH 04/16] Add tests for modify order
---
assets/images/coverage.svg | 4 +-
tests/test_exchange.py | 124 +++++++++++++++++++++++++++++++++++++
2 files changed, 126 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index d1f8965..f9eb6b4 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 53%
- 53%
+ 55%
+ 55%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index e952540..6222b30 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -309,3 +309,127 @@ def test_order(mock_bulk_orders, exchange):
mock_bulk_orders.assert_called_once()
order_request = mock_bulk_orders.call_args[0][0][0]
assert order_request["cloid"] == cloid
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_modify_order(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test modify_order method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+ exchange.info.name_to_asset = lambda x: 1
+
+ # Test 1: Basic modify order with oid
+ oid = 12345
+ response = exchange.modify_order(
+ oid=oid,
+ name="ETH",
+ is_buy=True,
+ sz=0.1,
+ limit_px=1105,
+ order_type={"limit": {"tif": "Gtc"}},
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "batchModify"
+ assert len(action["modifies"]) == 1
+ assert action["modifies"][0]["oid"] == oid
+
+ # Test 2: Modify order with cloid
+ mock_sign.reset_mock()
+ mock_post_action.reset_mock()
+
+ from hyperliquid.utils.types import Cloid
+ cloid = Cloid.from_str("0x00000000000000000000000000000001")
+ new_cloid = Cloid.from_str("0x00000000000000000000000000000002")
+
+ response = exchange.modify_order(
+ oid=cloid,
+ name="ETH",
+ is_buy=True,
+ sz=0.1,
+ limit_px=1105,
+ order_type={"limit": {"tif": "Gtc"}},
+ reduce_only=True,
+ cloid=new_cloid
+ )
+
+ assert response == {"status": "ok"}
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["modifies"][0]["oid"] == cloid.to_raw()
+ assert action["modifies"][0]["order"]["r"] is True # reduce_only
+ assert "c" in action["modifies"][0]["order"] # cloid in wire format
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_bulk_modify_orders_new(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test bulk_modify_orders_new method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+ exchange.info.name_to_asset = lambda x: 1
+
+ from hyperliquid.utils.types import Cloid
+ cloid1 = Cloid.from_str("0x00000000000000000000000000000001")
+ cloid2 = Cloid.from_str("0x00000000000000000000000000000002")
+
+ # Test multiple order modifications
+ modify_requests = [
+ {
+ "oid": 12345,
+ "order": {
+ "coin": "ETH",
+ "is_buy": True,
+ "sz": 0.1,
+ "limit_px": 1105,
+ "order_type": {"limit": {"tif": "Gtc"}},
+ "reduce_only": False,
+ "cloid": None,
+ },
+ },
+ {
+ "oid": cloid1,
+ "order": {
+ "coin": "BTC",
+ "is_buy": False,
+ "sz": 1.0,
+ "limit_px": 50000,
+ "order_type": {"limit": {"tif": "Ioc"}},
+ "reduce_only": True,
+ "cloid": cloid2,
+ },
+ },
+ ]
+
+ response = exchange.bulk_modify_orders_new(modify_requests)
+ assert response == {"status": "ok"}
+
+ # Verify the action structure
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+
+ assert action["type"] == "batchModify"
+ assert len(action["modifies"]) == 2
+
+ # Verify first modification
+ assert action["modifies"][0]["oid"] == 12345
+ assert action["modifies"][0]["order"]["s"] == "0.1" # size as string
+ assert action["modifies"][0]["order"]["p"] == "1105" # price as string
+
+ # Verify second modification
+ assert action["modifies"][1]["oid"] == cloid1.to_raw()
+ assert action["modifies"][1]["order"]["s"] == "1" # size as string
+ assert action["modifies"][1]["order"]["p"] == "50000" # price as string
+ assert action["modifies"][1]["order"]["r"] is True
+ assert "c" in action["modifies"][1]["order"] # cloid in wire format
From 566a30d0e1a21bd425b41fec6c2370b2b5e26896 Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:31:09 -0800
Subject: [PATCH 05/16] Add tests for market open and market close
---
assets/images/coverage.svg | 4 +-
tests/test_exchange.py | 116 +++++++++++++++++++++++++++++++++++++
2 files changed, 118 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index f9eb6b4..4713f1f 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 55%
- 55%
+ 57%
+ 57%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index 6222b30..c399061 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -433,3 +433,119 @@ def test_bulk_modify_orders_new(mock_post_action, mock_timestamp, mock_sign, exc
assert action["modifies"][1]["order"]["p"] == "50000" # price as string
assert action["modifies"][1]["order"]["r"] is True
assert "c" in action["modifies"][1]["order"] # cloid in wire format
+
+@patch('hyperliquid.exchange.Exchange.bulk_orders')
+def test_market_open(mock_bulk_orders, exchange):
+ """Test market_open method"""
+ # Setup
+ mock_bulk_orders.return_value = {
+ "status": "ok",
+ "response": {
+ "data": {
+ "statuses": [{"filled": {"oid": 123, "totalSz": "0.05", "avgPx": "1950.5"}}]
+ }
+ }
+ }
+ exchange.info.name_to_asset = lambda x: 1
+
+ # Test 1: Basic market open
+ response = exchange.market_open(
+ name="ETH",
+ is_buy=True,
+ sz=0.05,
+ )
+
+ assert response["status"] == "ok"
+ assert response["response"]["data"]["statuses"][0]["filled"]["oid"] == 123
+
+ mock_bulk_orders.assert_called_once()
+ order_request = mock_bulk_orders.call_args[0][0][0]
+ assert order_request["coin"] == "ETH"
+ assert order_request["is_buy"] is True
+ assert order_request["sz"] == 0.05
+ assert order_request["order_type"] == {"limit": {"tif": "Ioc"}}
+ assert order_request["reduce_only"] is False
+
+ # Test 2: Market open with slippage and builder
+ mock_bulk_orders.reset_mock()
+ builder = {"b": "0x8c967E73E7B15087c42A10D344cFf4c96D877f1D", "r": 0.001}
+
+ response = exchange.market_open(
+ name="ETH",
+ is_buy=True,
+ sz=0.05,
+ builder=builder,
+ slippage=0.01 # 1% slippage
+ )
+
+ assert response["status"] == "ok"
+ mock_bulk_orders.assert_called_once()
+ order_request = mock_bulk_orders.call_args[0][0][0]
+ assert order_request["coin"] == "ETH"
+ assert order_request["sz"] == 0.05
+ assert order_request["order_type"] == {"limit": {"tif": "Ioc"}}
+ # Verify builder was passed correctly
+ assert mock_bulk_orders.call_args[0][1]["b"].lower() == builder["b"].lower()
+ assert mock_bulk_orders.call_args[0][1]["r"] == builder["r"]
+
+@patch('hyperliquid.exchange.Exchange.bulk_orders')
+def test_market_close(mock_bulk_orders, exchange):
+ """Test market_close method"""
+ # Setup
+ mock_bulk_orders.return_value = {
+ "status": "ok",
+ "response": {
+ "data": {
+ "statuses": [{"filled": {"oid": 123, "totalSz": "0.05", "avgPx": "1950.5"}}]
+ }
+ }
+ }
+ exchange.info.name_to_asset = lambda x: 1
+
+ # Mock user_state to return a position
+ exchange.info.user_state = lambda x: {
+ "assetPositions": [
+ {
+ "position": {
+ "coin": "ETH",
+ "szi": "0.05",
+ "entryPx": "2000",
+ "positionValue": "100"
+ }
+ }
+ ]
+ }
+
+ # Test 1: Basic market close
+ response = exchange.market_close("ETH")
+
+ assert response["status"] == "ok"
+ assert response["response"]["data"]["statuses"][0]["filled"]["oid"] == 123
+
+ mock_bulk_orders.assert_called_once()
+ order_request = mock_bulk_orders.call_args[0][0][0]
+ assert order_request["coin"] == "ETH"
+ assert order_request["sz"] == 0.05
+ assert order_request["order_type"] == {"limit": {"tif": "Ioc"}}
+ assert order_request["reduce_only"] is True
+
+ # Test 2: Market close with slippage and builder
+ mock_bulk_orders.reset_mock()
+ builder = {"b": "0x8c967E73E7B15087c42A10D344cFf4c96D877f1D", "r": 0.001}
+
+ response = exchange.market_close(
+ coin="ETH",
+ builder=builder,
+ slippage=0.01 # 1% slippage
+ )
+
+ assert response["status"] == "ok"
+ mock_bulk_orders.assert_called_once()
+ order_request = mock_bulk_orders.call_args[0][0][0]
+ assert order_request["coin"] == "ETH"
+ assert order_request["sz"] == 0.05
+ assert order_request["order_type"] == {"limit": {"tif": "Ioc"}}
+ assert order_request["reduce_only"] is True
+ # Verify builder was passed correctly
+ assert mock_bulk_orders.call_args[0][1]["b"].lower() == builder["b"].lower()
+ assert mock_bulk_orders.call_args[0][1]["r"] == builder["r"]
From c726c1a7aa040422ed9ec5d58992ab95ada6d875 Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:33:41 -0800
Subject: [PATCH 06/16] Add tests for cancel, cancel_by_cloid, bulk_cancel, and
bulk_cancel_by_cloid
---
assets/images/coverage.svg | 4 +-
tests/test_exchange.py | 119 +++++++++++++++++++++++++++++++++++++
2 files changed, 121 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index 4713f1f..eb83f3f 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 57%
- 57%
+ 59%
+ 59%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index c399061..9a5ab51 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -549,3 +549,122 @@ def test_market_close(mock_bulk_orders, exchange):
# Verify builder was passed correctly
assert mock_bulk_orders.call_args[0][1]["b"].lower() == builder["b"].lower()
assert mock_bulk_orders.call_args[0][1]["r"] == builder["r"]
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test cancel method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+ exchange.info.name_to_asset = lambda x: 1
+
+ # Test basic cancel
+ response = exchange.cancel("ETH", 12345)
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "cancel"
+ assert len(action["cancels"]) == 1
+ assert action["cancels"][0]["a"] == 1 # asset
+ assert action["cancels"][0]["o"] == 12345 # oid
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_cancel_by_cloid(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test cancel_by_cloid method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+ exchange.info.name_to_asset = lambda x: 1
+
+ from hyperliquid.utils.types import Cloid
+ cloid = Cloid.from_str("0x00000000000000000000000000000001")
+
+ # Test cancel by cloid
+ response = exchange.cancel_by_cloid("ETH", cloid)
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "cancelByCloid"
+ assert len(action["cancels"]) == 1
+ assert action["cancels"][0]["asset"] == 1
+ assert action["cancels"][0]["cloid"] == cloid.to_raw()
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_bulk_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test bulk_cancel method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+ exchange.info.name_to_asset = lambda x: 1
+
+ # Test multiple cancels
+ cancel_requests = [
+ {"coin": "ETH", "oid": 12345},
+ {"coin": "BTC", "oid": 67890}
+ ]
+
+ response = exchange.bulk_cancel(cancel_requests)
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "cancel"
+ assert len(action["cancels"]) == 2
+ assert action["cancels"][0]["a"] == 1
+ assert action["cancels"][0]["o"] == 12345
+ assert action["cancels"][1]["a"] == 1
+ assert action["cancels"][1]["o"] == 67890
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_bulk_cancel_by_cloid(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test bulk_cancel_by_cloid method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+ exchange.info.name_to_asset = lambda x: 1
+
+ from hyperliquid.utils.types import Cloid
+ cloid1 = Cloid.from_str("0x00000000000000000000000000000001")
+ cloid2 = Cloid.from_str("0x00000000000000000000000000000002")
+
+ # Test multiple cancels by cloid
+ cancel_requests = [
+ {"coin": "ETH", "cloid": cloid1},
+ {"coin": "BTC", "cloid": cloid2}
+ ]
+
+ response = exchange.bulk_cancel_by_cloid(cancel_requests)
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "cancelByCloid"
+ assert len(action["cancels"]) == 2
+ assert action["cancels"][0]["asset"] == 1
+ assert action["cancels"][0]["cloid"] == cloid1.to_raw()
+ assert action["cancels"][1]["asset"] == 1
+ assert action["cancels"][1]["cloid"] == cloid2.to_raw()
From 415a5c117ca0354a277b087acb2c308d6f9b9791 Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:37:31 -0800
Subject: [PATCH 07/16] Add tests for schedule_cancel
---
assets/images/coverage.svg | 6 ++---
hyperliquid/exchange.py | 2 +-
tests/test_exchange.py | 48 ++++++++++++++++++++++++++++++++++++++
3 files changed, 52 insertions(+), 4 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index eb83f3f..9f708fe 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -9,13 +9,13 @@
-
+
coverage
coverage
- 59%
- 59%
+ 60%
+ 60%
diff --git a/hyperliquid/exchange.py b/hyperliquid/exchange.py
index f0c5d47..662c11d 100644
--- a/hyperliquid/exchange.py
+++ b/hyperliquid/exchange.py
@@ -297,7 +297,7 @@ def bulk_cancel_by_cloid(self, cancel_requests: List[CancelByCloidRequest]) -> A
timestamp,
)
- def schedule_cancel(self, time: Optional[int]) -> Any:
+ def schedule_cancel(self, time: Optional[int] = None) -> Any:
"""Schedules a time (in UTC millis) to cancel all open orders. The time must be at least 5 seconds after the current time.
Once the time comes, all open orders will be canceled and a trigger count will be incremented. The max number of triggers
per day is 10. This trigger count is reset at 00:00 UTC.
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index 9a5ab51..53c8770 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -668,3 +668,51 @@ def test_bulk_cancel_by_cloid(mock_post_action, mock_timestamp, mock_sign, excha
assert action["cancels"][0]["cloid"] == cloid1.to_raw()
assert action["cancels"][1]["asset"] == 1
assert action["cancels"][1]["cloid"] == cloid2.to_raw()
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_schedule_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test schedule_cancel method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test 1: Basic schedule cancel without time (uses current timestamp)
+ response = exchange.schedule_cancel()
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "scheduleCancel"
+ assert "time" not in action # No specific time provided
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ assert call_args[1] == "test_signature" # signature
+
+ # Test 2: Schedule cancel with specific time
+ mock_sign.reset_mock()
+ mock_post_action.reset_mock()
+
+ cancel_time = 1234567890 + 10000 # 10 seconds from now
+ response = exchange.schedule_cancel(cancel_time)
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "scheduleCancel"
+ assert action["time"] == cancel_time
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ assert call_args[1] == "test_signature" # signature
From 3bf0c1c0170e45e3526c9599a53e8c38fbe13e2f Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:39:25 -0800
Subject: [PATCH 08/16] Add tests for update_leverage
---
tests/test_exchange.py | 56 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index 53c8770..80f81e1 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -716,3 +716,59 @@ def test_schedule_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_update_leverage(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test update_leverage method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+ exchange.info.name_to_asset = lambda x: 1
+
+ # Test 1: Update leverage with default cross margin
+ response = exchange.update_leverage(leverage=10, name="ETH")
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "updateLeverage"
+ assert action["asset"] == 1
+ assert action["leverage"] == 10
+ assert action["isCross"] is True
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ assert call_args[1] == "test_signature" # signature
+
+ # Test 2: Update leverage with isolated margin
+ mock_sign.reset_mock()
+ mock_post_action.reset_mock()
+
+ response = exchange.update_leverage(
+ leverage=5,
+ name="BTC",
+ is_cross=False
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "updateLeverage"
+ assert action["asset"] == 1
+ assert action["leverage"] == 5
+ assert action["isCross"] is False
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ assert call_args[1] == "test_signature" # signature
From cdc45bde7096e1cd8d2399d926bbc92fe60ffb1b Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:43:23 -0800
Subject: [PATCH 09/16] Add tests for update_isolated_margin
---
assets/images/coverage.svg | 4 ++--
tests/test_exchange.py | 33 +++++++++++++++++++++++++++++++++
2 files changed, 35 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index 9f708fe..dd6df12 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 60%
- 60%
+ 61%
+ 61%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index 80f81e1..7842896 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -772,3 +772,36 @@ def test_update_leverage(mock_post_action, mock_timestamp, mock_sign, exchange):
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_update_isolated_margin(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test update_isolated_margin method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+ exchange.info.name_to_asset = lambda x: 1
+
+ # Test: Update isolated margin
+ response = exchange.update_isolated_margin(
+ amount=1000.0,
+ name="ETH"
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "updateIsolatedMargin"
+ assert action["asset"] == 1
+ assert action["isBuy"] is True # isBuy is always True in the implementation
+ assert "ntli" in action # ntli (notional) should be present
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ assert call_args[1] == "test_signature" # signature
From 9adf93dfc542a70850de21f11bb13e4925cf6a2d Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:45:28 -0800
Subject: [PATCH 10/16] Add tests for set_referrer, create_sub_account
---
assets/images/coverage.svg | 4 +--
tests/test_exchange.py | 54 ++++++++++++++++++++++++++++++++++++++
2 files changed, 56 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index dd6df12..2fad913 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 61%
- 61%
+ 62%
+ 62%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index 7842896..3c04420 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -805,3 +805,57 @@ def test_update_isolated_margin(mock_post_action, mock_timestamp, mock_sign, exc
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_set_referrer(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test set_referrer method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test setting referrer code
+ response = exchange.set_referrer("ASDFASDF")
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "setReferrer"
+ assert action["code"] == "ASDFASDF"
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ assert call_args[1] == "test_signature" # signature
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_create_sub_account(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test create_sub_account method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test creating sub account
+ response = exchange.create_sub_account("example")
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "createSubAccount"
+ assert action["name"] == "example"
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ assert call_args[1] == "test_signature" # signature
From 97b82d5c38bbf846ec9f092111f9cf60e554f77f Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 11:48:43 -0800
Subject: [PATCH 11/16] Add tests for usd_class_transfer and
sub_account_transfer
---
assets/images/coverage.svg | 4 +-
tests/test_exchange.py | 92 ++++++++++++++++++++++++++++++++++++++
2 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index 2fad913..6d68f47 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 62%
- 62%
+ 64%
+ 64%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index 3c04420..02f94d1 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -859,3 +859,95 @@ def test_create_sub_account(mock_post_action, mock_timestamp, mock_sign, exchang
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
+
+@patch('hyperliquid.exchange.sign_usd_class_transfer_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_usd_class_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test usd_class_transfer method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test 1: Basic transfer without vault address
+ response = exchange.usd_class_transfer(amount=1000.0, to_perp=True)
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_usd_class_transfer_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "usdClassTransfer"
+ assert action["amount"] == "1000.0"
+ assert action["toPerp"] is True
+ assert action["nonce"] == 1234567890
+
+ # Test 2: Transfer with vault address
+ mock_sign.reset_mock()
+ mock_post_action.reset_mock()
+ exchange.vault_address = "0x1234"
+
+ response = exchange.usd_class_transfer(amount=500.5, to_perp=False)
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_usd_class_transfer_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "usdClassTransfer"
+ assert action["amount"] == "500.5 subaccount:0x1234"
+ assert action["toPerp"] is False
+ assert action["nonce"] == 1234567890
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_sub_account_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test sub_account_transfer method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test deposit to sub account
+ sub_account = "0x1d9470d4b963f552e6f671a81619d395877bf409"
+ response = exchange.sub_account_transfer(
+ sub_account_user=sub_account,
+ is_deposit=True,
+ usd=1000
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "subAccountTransfer"
+ assert action["subAccountUser"] == sub_account
+ assert action["isDeposit"] is True
+ assert action["usd"] == 1000
+
+ # Test withdrawal from sub account
+ mock_sign.reset_mock()
+ mock_post_action.reset_mock()
+
+ response = exchange.sub_account_transfer(
+ sub_account_user=sub_account,
+ is_deposit=False,
+ usd=500
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "subAccountTransfer"
+ assert action["subAccountUser"] == sub_account
+ assert action["isDeposit"] is False
+ assert action["usd"] == 500
From 3cf83574ef3809aba36e3dd276ae3c5d542030e3 Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 12:02:17 -0800
Subject: [PATCH 12/16] Add tests for vault_usd_transfer and usd_transfer
---
assets/images/coverage.svg | 4 +--
tests/test_exchange.py | 70 ++++++++++++++++++++++++++++++++++++++
2 files changed, 72 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index 6d68f47..c97e504 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 64%
- 64%
+ 66%
+ 66%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index 02f94d1..e8da953 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -951,3 +951,73 @@ def test_sub_account_transfer(mock_post_action, mock_timestamp, mock_sign, excha
assert action["subAccountUser"] == sub_account
assert action["isDeposit"] is False
assert action["usd"] == 500
+
+@patch('hyperliquid.exchange.sign_l1_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_vault_usd_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test vault_usd_transfer method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test vault transfer
+ vault_address = "0xa15099a30bbf2e68942d6f4c43d70d04faeab0a0"
+ response = exchange.vault_usd_transfer(
+ vault_address=vault_address,
+ is_deposit=True,
+ usd=5_000_000
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_l1_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "vaultTransfer"
+ assert action["vaultAddress"] == vault_address
+ assert action["isDeposit"] is True
+ assert action["usd"] == 5_000_000
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ assert call_args[1] == "test_signature" # signature
+
+@patch('hyperliquid.exchange.sign_usd_transfer_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_usd_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test usd_transfer method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test USD transfer
+ destination = "0x5e9ee1089755c3435139848e47e6635505d5a13a"
+ response = exchange.usd_transfer(
+ destination=destination,
+ amount=1000.0
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_usd_transfer_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ message = call_args[1]
+ assert message["destination"] == destination
+ assert message["amount"] == "1000.0"
+ assert message["time"] == 1234567890
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ action = call_args[0]
+ assert action["type"] == "usdSend" # Corrected action type
+ assert action["destination"] == destination
+ assert action["amount"] == "1000.0"
+ assert action["time"] == 1234567890
From 66640335a1f7499bb7d59dc4aeaa4758e11b7768 Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 12:08:37 -0800
Subject: [PATCH 13/16] Add tests for spot_transfer and withdraw_from_bridge
---
assets/images/coverage.svg | 4 +-
tests/test_exchange.py | 77 ++++++++++++++++++++++++++++++++++++++
2 files changed, 79 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index c97e504..dfb99a1 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 66%
- 66%
+ 67%
+ 67%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index e8da953..d51df29 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -1021,3 +1021,80 @@ def test_usd_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["destination"] == destination
assert action["amount"] == "1000.0"
assert action["time"] == 1234567890
+
+@patch('hyperliquid.exchange.sign_spot_transfer_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_spot_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test spot_transfer method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test spot transfer
+ destination = "0x5e9ee1089755c3435139848e47e6635505d5a13a"
+ response = exchange.spot_transfer(
+ amount=1000.0,
+ destination=destination,
+ token="ETH"
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_spot_transfer_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ message = call_args[1]
+ assert message["destination"] == destination
+ assert message["amount"] == "1000.0"
+ assert message["token"] == "ETH"
+ assert message["time"] == 1234567890
+ assert message["type"] == "spotSend"
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ action = call_args[0]
+ assert action["type"] == "spotSend"
+ assert action["destination"] == destination
+ assert action["amount"] == "1000.0"
+ assert action["token"] == "ETH"
+ assert action["time"] == 1234567890
+
+@patch('hyperliquid.exchange.sign_withdraw_from_bridge_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_withdraw_from_bridge(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test withdraw_from_bridge method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test bridge withdrawal
+ destination = "0x5e9ee1089755c3435139848e47e6635505d5a13a"
+ response = exchange.withdraw_from_bridge(
+ amount=1000.0,
+ destination=destination
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_withdraw_from_bridge_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "withdraw3"
+ assert action["destination"] == destination
+ assert action["amount"] == "1000.0"
+ assert action["time"] == 1234567890
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ action = call_args[0]
+ assert action["type"] == "withdraw3"
+ assert action["destination"] == destination
+ assert action["amount"] == "1000.0"
+ assert action["time"] == 1234567890
From b806e8dbd836a9bcf76133bf10d9fc4abd91a069 Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 12:13:37 -0800
Subject: [PATCH 14/16] Add tests for test_approve_agent and
test_approve_builder_fee
---
assets/images/coverage.svg | 4 +-
tests/test_exchange.py | 82 ++++++++++++++++++++++++++++++++++++++
2 files changed, 84 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index dfb99a1..a4262d3 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 67%
- 67%
+ 69%
+ 69%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index d51df29..7708efc 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -1098,3 +1098,85 @@ def test_withdraw_from_bridge(mock_post_action, mock_timestamp, mock_sign, excha
assert action["destination"] == destination
assert action["amount"] == "1000.0"
assert action["time"] == 1234567890
+
+@patch('hyperliquid.exchange.sign_agent')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+@patch('hyperliquid.exchange.secrets.token_hex')
+def test_approve_agent(mock_token_hex, mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test approve_agent method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+ # Generate a 32-byte hex string (64 characters)
+ mock_token_hex.return_value = "a" * 64
+
+ # Test 1: approve agent without name
+ response, agent_key = exchange.approve_agent()
+
+ assert response == {"status": "ok"}
+ assert agent_key == "0x" + ("a" * 64)
+
+ # Verify sign_agent was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "approveAgent"
+ assert "agentAddress" in action
+ assert action["nonce"] == 1234567890
+ assert "agentName" not in action
+
+ # Test 2: approve agent with name
+ mock_sign.reset_mock()
+ mock_post_action.reset_mock()
+
+ response, agent_key = exchange.approve_agent(name="test_agent")
+
+ assert response == {"status": "ok"}
+ assert agent_key == "0x" + ("a" * 64)
+
+ # Verify sign_agent was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "approveAgent"
+ assert "agentAddress" in action
+ assert action["nonce"] == 1234567890
+ assert action["agentName"] == "test_agent"
+
+@patch('hyperliquid.exchange.sign_approve_builder_fee')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_approve_builder_fee(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test approve_builder_fee method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test approving builder fee
+ response = exchange.approve_builder_fee(
+ builder="0x1234567890123456789012345678901234567890",
+ max_fee_rate="0.001"
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_approve_builder_fee was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "approveBuilderFee"
+ assert action["builder"] == "0x1234567890123456789012345678901234567890"
+ assert action["maxFeeRate"] == "0.001"
+ assert action["nonce"] == 1234567890
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ action = call_args[0]
+ assert action["type"] == "approveBuilderFee"
+ assert action["builder"] == "0x1234567890123456789012345678901234567890"
+ assert action["maxFeeRate"] == "0.001"
+ assert action["nonce"] == 1234567890
From 969de810c6d491c62583ab897f672b3017b0a539 Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 12:15:51 -0800
Subject: [PATCH 15/16] Add tests for convert_to_multi_sig_user and multi_sig
---
assets/images/coverage.svg | 4 +-
tests/test_exchange.py | 82 ++++++++++++++++++++++++++++++++++++++
2 files changed, 84 insertions(+), 2 deletions(-)
diff --git a/assets/images/coverage.svg b/assets/images/coverage.svg
index a4262d3..ffd257b 100644
--- a/assets/images/coverage.svg
+++ b/assets/images/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 69%
- 69%
+ 71%
+ 71%
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index 7708efc..e72001a 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -2,6 +2,7 @@
from eth_account import Account
from eth_account.signers.local import LocalAccount
from unittest.mock import Mock, patch
+import json
from hyperliquid.exchange import Exchange
from hyperliquid.utils.constants import MAINNET_API_URL
@@ -1180,3 +1181,84 @@ def test_approve_builder_fee(mock_post_action, mock_timestamp, mock_sign, exchan
assert action["builder"] == "0x1234567890123456789012345678901234567890"
assert action["maxFeeRate"] == "0.001"
assert action["nonce"] == 1234567890
+
+@patch('hyperliquid.exchange.sign_convert_to_multi_sig_user_action')
+@patch('hyperliquid.exchange.get_timestamp_ms')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_convert_to_multi_sig_user(mock_post_action, mock_timestamp, mock_sign, exchange):
+ """Test convert_to_multi_sig_user method"""
+ # Setup
+ mock_timestamp.return_value = 1234567890
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test converting to multi-sig user
+ authorized_users = [
+ "0x0000000000000000000000000000000000000001",
+ "0x0000000000000000000000000000000000000000" # Note: Will be sorted
+ ]
+ threshold = 1
+ response = exchange.convert_to_multi_sig_user(authorized_users, threshold)
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_convert_to_multi_sig_user_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "convertToMultiSigUser"
+ assert action["nonce"] == 1234567890
+
+ # Verify signers JSON structure
+ signers = json.loads(action["signers"])
+ assert signers["threshold"] == 1
+ assert signers["authorizedUsers"] == sorted(authorized_users)
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ assert call_args[1] == "test_signature"
+
+@patch('hyperliquid.exchange.sign_multi_sig_action')
+@patch('hyperliquid.exchange.Exchange._post_action')
+def test_multi_sig(mock_post_action, mock_sign, exchange):
+ """Test multi_sig method"""
+ # Setup
+ mock_sign.return_value = "test_signature"
+ mock_post_action.return_value = {"status": "ok"}
+
+ # Test parameters
+ multi_sig_user = "0xABCD"
+ inner_action = {"type": "order", "data": "test"}
+ signatures = ["sig1", "sig2"]
+ nonce = 1234567890
+ vault_address = "0x1234"
+
+ # Test multi-sig action
+ response = exchange.multi_sig(
+ multi_sig_user=multi_sig_user,
+ inner_action=inner_action,
+ signatures=signatures,
+ nonce=nonce,
+ vault_address=vault_address
+ )
+
+ assert response == {"status": "ok"}
+
+ # Verify sign_multi_sig_action was called correctly
+ mock_sign.assert_called_once()
+ call_args = mock_sign.call_args[0]
+ action = call_args[1]
+ assert action["type"] == "multiSig"
+ assert action["signatureChainId"] == "0x66eee"
+ assert action["signatures"] == signatures
+ assert action["payload"]["multiSigUser"] == multi_sig_user.lower()
+ assert action["payload"]["outerSigner"] == exchange.wallet.address.lower()
+ assert action["payload"]["action"] == inner_action
+
+ # Verify _post_action was called correctly
+ mock_post_action.assert_called_once()
+ call_args = mock_post_action.call_args[0]
+ assert call_args[0] == action
+ assert call_args[1] == "test_signature"
+ assert call_args[2] == nonce
From 4ca46bbaf13fb67148fd27fa5cf467f9aa8b127c Mon Sep 17 00:00:00 2001
From: perplover <184728147+perplover@users.noreply.github.com>
Date: Tue, 14 Jan 2025 12:16:16 -0800
Subject: [PATCH 16/16] Run make codestyle
---
tests/test_exchange.py | 574 ++++++++++++++++++-----------------------
1 file changed, 255 insertions(+), 319 deletions(-)
diff --git a/tests/test_exchange.py b/tests/test_exchange.py
index e72001a..fb6978d 100644
--- a/tests/test_exchange.py
+++ b/tests/test_exchange.py
@@ -1,8 +1,9 @@
+import json
+from unittest.mock import Mock, patch
+
import pytest
from eth_account import Account
from eth_account.signers.local import LocalAccount
-from unittest.mock import Mock, patch
-import json
from hyperliquid.exchange import Exchange
from hyperliquid.utils.constants import MAINNET_API_URL
@@ -11,16 +12,19 @@
TEST_META: Meta = {"universe": []}
TEST_SPOT_META: SpotMeta = {"universe": [], "tokens": []}
+
@pytest.fixture
def wallet() -> LocalAccount:
"""Create a test wallet"""
return Account.from_key("0x0123456789012345678901234567890123456789012345678901234567890123")
+
@pytest.fixture
def exchange(wallet):
"""Fixture that provides an Exchange instance"""
return Exchange(wallet)
+
def test_initializer(exchange, wallet):
"""Test that the Exchange class initializes with correct default values"""
assert exchange.base_url == MAINNET_API_URL
@@ -29,28 +33,30 @@ def test_initializer(exchange, wallet):
assert exchange.account_address is None
assert exchange.info is not None
+
def test_initializer_with_custom_values(wallet):
"""Test that the Exchange class can be initialized with custom values"""
custom_url = "https://custom.api.url"
vault_address = "0x1234567890123456789012345678901234567890"
account_address = "0x0987654321098765432109876543210987654321"
-
+
exchange = Exchange(
wallet=wallet,
base_url=custom_url,
meta=TEST_META,
vault_address=vault_address,
account_address=account_address,
- spot_meta=TEST_SPOT_META
+ spot_meta=TEST_SPOT_META,
)
-
+
assert exchange.base_url == custom_url
assert exchange.wallet == wallet
assert exchange.vault_address == vault_address
assert exchange.account_address == account_address
assert exchange.info is not None
-@patch('hyperliquid.api.API.post')
+
+@patch("hyperliquid.api.API.post")
def test_post_action(mock_post, exchange):
"""Test _post_action method"""
# Setup
@@ -62,13 +68,7 @@ def test_post_action(mock_post, exchange):
# Test with regular action
response = exchange._post_action(action, signature, nonce)
mock_post.assert_called_once_with(
- "/exchange",
- {
- "action": action,
- "nonce": nonce,
- "signature": signature,
- "vaultAddress": None
- }
+ "/exchange", {"action": action, "nonce": nonce, "signature": signature, "vaultAddress": None}
)
assert response == {"status": "ok"}
@@ -77,13 +77,7 @@ def test_post_action(mock_post, exchange):
exchange.vault_address = "0x1234"
response = exchange._post_action(action, signature, nonce)
mock_post.assert_called_once_with(
- "/exchange",
- {
- "action": action,
- "nonce": nonce,
- "signature": signature,
- "vaultAddress": "0x1234"
- }
+ "/exchange", {"action": action, "nonce": nonce, "signature": signature, "vaultAddress": "0x1234"}
)
# Test with usdClassTransfer action
@@ -91,16 +85,11 @@ def test_post_action(mock_post, exchange):
action["type"] = "usdClassTransfer"
response = exchange._post_action(action, signature, nonce)
mock_post.assert_called_once_with(
- "/exchange",
- {
- "action": action,
- "nonce": nonce,
- "signature": signature,
- "vaultAddress": None
- }
+ "/exchange", {"action": action, "nonce": nonce, "signature": signature, "vaultAddress": None}
)
-@patch('hyperliquid.info.Info.all_mids')
+
+@patch("hyperliquid.info.Info.all_mids")
def test_slippage_price_perp(mock_all_mids, exchange):
"""Test _slippage_price method for perpetual contracts"""
# Setup
@@ -120,7 +109,8 @@ def test_slippage_price_perp(mock_all_mids, exchange):
price = exchange._slippage_price("ETH", True, 0.05, 1000.0)
assert price == 1050.0 # 1000 * (1 + 0.05)
-@patch('hyperliquid.info.Info.all_mids')
+
+@patch("hyperliquid.info.Info.all_mids")
def test_slippage_price_spot(mock_all_mids, exchange):
"""Test _slippage_price method for spot trading"""
# Setup
@@ -136,7 +126,8 @@ def test_slippage_price_spot(mock_all_mids, exchange):
price = exchange._slippage_price("BTC/USDC", False, 0.05)
assert price == 38000.0 # 40000 * (1 - 0.05)
-@patch('hyperliquid.info.Info.all_mids')
+
+@patch("hyperliquid.info.Info.all_mids")
def test_slippage_price_rounding(mock_all_mids, exchange):
"""Test price rounding in _slippage_price method"""
# Setup for perp
@@ -146,7 +137,7 @@ def test_slippage_price_rounding(mock_all_mids, exchange):
# Test perp rounding (6 decimals)
price = exchange._slippage_price("ETH", True, 0.05)
- assert str(price).count('.') == 0 or len(str(price).split('.')[1]) <= 6
+ assert str(price).count(".") == 0 or len(str(price).split(".")[1]) <= 6
# Setup for spot
mock_all_mids.return_value = {"BTC/USDC": "40000.123456789"}
@@ -155,11 +146,12 @@ def test_slippage_price_rounding(mock_all_mids, exchange):
# Test spot rounding (8 decimals)
price = exchange._slippage_price("BTC/USDC", True, 0.05)
- assert str(price).count('.') == 0 or len(str(price).split('.')[1]) <= 8
+ assert str(price).count(".") == 0 or len(str(price).split(".")[1]) <= 8
+
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_bulk_orders(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test bulk_orders method"""
# Setup
@@ -181,9 +173,9 @@ def test_bulk_orders(mock_post_action, mock_timestamp, mock_sign, exchange):
]
response = exchange.bulk_orders(order_requests)
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -195,20 +187,18 @@ def test_bulk_orders(mock_post_action, mock_timestamp, mock_sign, exchange):
# Verify _post_action was called correctly
mock_post_action.assert_called_once_with(
- mock_sign.call_args[0][1], # action
- "test_signature", # signature
- 1234567890 # timestamp
+ mock_sign.call_args[0][1], "test_signature", 1234567890 # action # signature # timestamp
)
# Test with builder
mock_sign.reset_mock()
mock_post_action.reset_mock()
-
+
builder = {"b": "TEST_BUILDER", "r": 0.001} # Using uppercase to test lowercasing
response = exchange.bulk_orders(order_requests, builder)
-
+
assert response == {"status": "ok"}
-
+
# Verify builder was included in the action
call_args = mock_sign.call_args[0]
action = call_args[1] # Get the action argument
@@ -217,18 +207,12 @@ def test_bulk_orders(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["builder"]["b"] == "test_builder"
assert action["builder"]["r"] == 0.001
-@patch('hyperliquid.exchange.Exchange.bulk_orders')
+
+@patch("hyperliquid.exchange.Exchange.bulk_orders")
def test_order(mock_bulk_orders, exchange):
"""Test order method with various scenarios"""
# Setup
- mock_bulk_orders.return_value = {
- "status": "ok",
- "response": {
- "data": {
- "statuses": [{"resting": {"oid": 123}}]
- }
- }
- }
+ mock_bulk_orders.return_value = {"status": "ok", "response": {"data": {"statuses": [{"resting": {"oid": 123}}]}}}
exchange.info.name_to_asset = lambda x: 1
# Test 1: Basic limit order
@@ -239,10 +223,10 @@ def test_order(mock_bulk_orders, exchange):
limit_px=2000.0,
order_type={"limit": {"tif": "Gtc"}},
)
-
+
assert response["status"] == "ok"
assert response["response"]["data"]["statuses"][0]["resting"]["oid"] == 123
-
+
mock_bulk_orders.assert_called_once_with(
[
{
@@ -254,22 +238,17 @@ def test_order(mock_bulk_orders, exchange):
"reduce_only": False,
}
],
- None
+ None,
)
# Test 2: Order with builder fee
mock_bulk_orders.reset_mock()
builder = {"b": "0x8c967E73E7B15087c42A10D344cFf4c96D877f1D", "r": 0.001}
-
+
response = exchange.order(
- name="ETH",
- is_buy=True,
- sz=0.05,
- limit_px=2000.0,
- order_type={"limit": {"tif": "Ioc"}},
- builder=builder
+ name="ETH", is_buy=True, sz=0.05, limit_px=2000.0, order_type={"limit": {"tif": "Ioc"}}, builder=builder
)
-
+
assert response["status"] == "ok"
mock_bulk_orders.assert_called_once()
call_args = mock_bulk_orders.call_args[0]
@@ -284,9 +263,9 @@ def test_order(mock_bulk_orders, exchange):
sz=100,
limit_px=100,
order_type={"trigger": {"triggerPx": 103, "isMarket": True, "tpsl": "sl"}},
- reduce_only=True
+ reduce_only=True,
)
-
+
assert response["status"] == "ok"
mock_bulk_orders.assert_called_once()
order_request = mock_bulk_orders.call_args[0][0][0]
@@ -296,24 +275,20 @@ def test_order(mock_bulk_orders, exchange):
# Test 4: Order with cloid
mock_bulk_orders.reset_mock()
cloid = "0x00000000000000000000000000000001"
-
+
response = exchange.order(
- name="ETH",
- is_buy=True,
- sz=1.0,
- limit_px=2000.0,
- order_type={"limit": {"tif": "Gtc"}},
- cloid=cloid
+ name="ETH", is_buy=True, sz=1.0, limit_px=2000.0, order_type={"limit": {"tif": "Gtc"}}, cloid=cloid
)
-
+
assert response["status"] == "ok"
mock_bulk_orders.assert_called_once()
order_request = mock_bulk_orders.call_args[0][0][0]
assert order_request["cloid"] == cloid
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_modify_order(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test modify_order method"""
# Setup
@@ -332,9 +307,9 @@ def test_modify_order(mock_post_action, mock_timestamp, mock_sign, exchange):
limit_px=1105,
order_type={"limit": {"tif": "Gtc"}},
)
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -342,15 +317,16 @@ def test_modify_order(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["type"] == "batchModify"
assert len(action["modifies"]) == 1
assert action["modifies"][0]["oid"] == oid
-
+
# Test 2: Modify order with cloid
mock_sign.reset_mock()
mock_post_action.reset_mock()
-
+
from hyperliquid.utils.types import Cloid
+
cloid = Cloid.from_str("0x00000000000000000000000000000001")
new_cloid = Cloid.from_str("0x00000000000000000000000000000002")
-
+
response = exchange.modify_order(
oid=cloid,
name="ETH",
@@ -359,9 +335,9 @@ def test_modify_order(mock_post_action, mock_timestamp, mock_sign, exchange):
limit_px=1105,
order_type={"limit": {"tif": "Gtc"}},
reduce_only=True,
- cloid=new_cloid
+ cloid=new_cloid,
)
-
+
assert response == {"status": "ok"}
call_args = mock_sign.call_args[0]
action = call_args[1]
@@ -369,9 +345,10 @@ def test_modify_order(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["modifies"][0]["order"]["r"] is True # reduce_only
assert "c" in action["modifies"][0]["order"] # cloid in wire format
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_bulk_modify_orders_new(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test bulk_modify_orders_new method"""
# Setup
@@ -381,6 +358,7 @@ def test_bulk_modify_orders_new(mock_post_action, mock_timestamp, mock_sign, exc
exchange.info.name_to_asset = lambda x: 1
from hyperliquid.utils.types import Cloid
+
cloid1 = Cloid.from_str("0x00000000000000000000000000000001")
cloid2 = Cloid.from_str("0x00000000000000000000000000000002")
@@ -411,23 +389,23 @@ def test_bulk_modify_orders_new(mock_post_action, mock_timestamp, mock_sign, exc
},
},
]
-
+
response = exchange.bulk_modify_orders_new(modify_requests)
assert response == {"status": "ok"}
-
+
# Verify the action structure
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
action = call_args[1]
-
+
assert action["type"] == "batchModify"
assert len(action["modifies"]) == 2
-
+
# Verify first modification
assert action["modifies"][0]["oid"] == 12345
assert action["modifies"][0]["order"]["s"] == "0.1" # size as string
assert action["modifies"][0]["order"]["p"] == "1105" # price as string
-
+
# Verify second modification
assert action["modifies"][1]["oid"] == cloid1.to_raw()
assert action["modifies"][1]["order"]["s"] == "1" # size as string
@@ -435,17 +413,14 @@ def test_bulk_modify_orders_new(mock_post_action, mock_timestamp, mock_sign, exc
assert action["modifies"][1]["order"]["r"] is True
assert "c" in action["modifies"][1]["order"] # cloid in wire format
-@patch('hyperliquid.exchange.Exchange.bulk_orders')
+
+@patch("hyperliquid.exchange.Exchange.bulk_orders")
def test_market_open(mock_bulk_orders, exchange):
"""Test market_open method"""
# Setup
mock_bulk_orders.return_value = {
"status": "ok",
- "response": {
- "data": {
- "statuses": [{"filled": {"oid": 123, "totalSz": "0.05", "avgPx": "1950.5"}}]
- }
- }
+ "response": {"data": {"statuses": [{"filled": {"oid": 123, "totalSz": "0.05", "avgPx": "1950.5"}}]}},
}
exchange.info.name_to_asset = lambda x: 1
@@ -455,10 +430,10 @@ def test_market_open(mock_bulk_orders, exchange):
is_buy=True,
sz=0.05,
)
-
+
assert response["status"] == "ok"
assert response["response"]["data"]["statuses"][0]["filled"]["oid"] == 123
-
+
mock_bulk_orders.assert_called_once()
order_request = mock_bulk_orders.call_args[0][0][0]
assert order_request["coin"] == "ETH"
@@ -466,19 +441,13 @@ def test_market_open(mock_bulk_orders, exchange):
assert order_request["sz"] == 0.05
assert order_request["order_type"] == {"limit": {"tif": "Ioc"}}
assert order_request["reduce_only"] is False
-
+
# Test 2: Market open with slippage and builder
mock_bulk_orders.reset_mock()
builder = {"b": "0x8c967E73E7B15087c42A10D344cFf4c96D877f1D", "r": 0.001}
-
- response = exchange.market_open(
- name="ETH",
- is_buy=True,
- sz=0.05,
- builder=builder,
- slippage=0.01 # 1% slippage
- )
-
+
+ response = exchange.market_open(name="ETH", is_buy=True, sz=0.05, builder=builder, slippage=0.01) # 1% slippage
+
assert response["status"] == "ok"
mock_bulk_orders.assert_called_once()
order_request = mock_bulk_orders.call_args[0][0][0]
@@ -489,57 +458,41 @@ def test_market_open(mock_bulk_orders, exchange):
assert mock_bulk_orders.call_args[0][1]["b"].lower() == builder["b"].lower()
assert mock_bulk_orders.call_args[0][1]["r"] == builder["r"]
-@patch('hyperliquid.exchange.Exchange.bulk_orders')
+
+@patch("hyperliquid.exchange.Exchange.bulk_orders")
def test_market_close(mock_bulk_orders, exchange):
"""Test market_close method"""
# Setup
mock_bulk_orders.return_value = {
"status": "ok",
- "response": {
- "data": {
- "statuses": [{"filled": {"oid": 123, "totalSz": "0.05", "avgPx": "1950.5"}}]
- }
- }
+ "response": {"data": {"statuses": [{"filled": {"oid": 123, "totalSz": "0.05", "avgPx": "1950.5"}}]}},
}
exchange.info.name_to_asset = lambda x: 1
-
+
# Mock user_state to return a position
exchange.info.user_state = lambda x: {
- "assetPositions": [
- {
- "position": {
- "coin": "ETH",
- "szi": "0.05",
- "entryPx": "2000",
- "positionValue": "100"
- }
- }
- ]
+ "assetPositions": [{"position": {"coin": "ETH", "szi": "0.05", "entryPx": "2000", "positionValue": "100"}}]
}
# Test 1: Basic market close
response = exchange.market_close("ETH")
-
+
assert response["status"] == "ok"
assert response["response"]["data"]["statuses"][0]["filled"]["oid"] == 123
-
+
mock_bulk_orders.assert_called_once()
order_request = mock_bulk_orders.call_args[0][0][0]
assert order_request["coin"] == "ETH"
assert order_request["sz"] == 0.05
assert order_request["order_type"] == {"limit": {"tif": "Ioc"}}
assert order_request["reduce_only"] is True
-
+
# Test 2: Market close with slippage and builder
mock_bulk_orders.reset_mock()
builder = {"b": "0x8c967E73E7B15087c42A10D344cFf4c96D877f1D", "r": 0.001}
-
- response = exchange.market_close(
- coin="ETH",
- builder=builder,
- slippage=0.01 # 1% slippage
- )
-
+
+ response = exchange.market_close(coin="ETH", builder=builder, slippage=0.01) # 1% slippage
+
assert response["status"] == "ok"
mock_bulk_orders.assert_called_once()
order_request = mock_bulk_orders.call_args[0][0][0]
@@ -551,9 +504,10 @@ def test_market_close(mock_bulk_orders, exchange):
assert mock_bulk_orders.call_args[0][1]["b"].lower() == builder["b"].lower()
assert mock_bulk_orders.call_args[0][1]["r"] == builder["r"]
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test cancel method"""
# Setup
@@ -564,9 +518,9 @@ def test_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
# Test basic cancel
response = exchange.cancel("ETH", 12345)
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -576,9 +530,10 @@ def test_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["cancels"][0]["a"] == 1 # asset
assert action["cancels"][0]["o"] == 12345 # oid
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_cancel_by_cloid(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test cancel_by_cloid method"""
# Setup
@@ -588,13 +543,14 @@ def test_cancel_by_cloid(mock_post_action, mock_timestamp, mock_sign, exchange):
exchange.info.name_to_asset = lambda x: 1
from hyperliquid.utils.types import Cloid
+
cloid = Cloid.from_str("0x00000000000000000000000000000001")
# Test cancel by cloid
response = exchange.cancel_by_cloid("ETH", cloid)
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -604,9 +560,10 @@ def test_cancel_by_cloid(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["cancels"][0]["asset"] == 1
assert action["cancels"][0]["cloid"] == cloid.to_raw()
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_bulk_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test bulk_cancel method"""
# Setup
@@ -616,14 +573,11 @@ def test_bulk_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
exchange.info.name_to_asset = lambda x: 1
# Test multiple cancels
- cancel_requests = [
- {"coin": "ETH", "oid": 12345},
- {"coin": "BTC", "oid": 67890}
- ]
-
+ cancel_requests = [{"coin": "ETH", "oid": 12345}, {"coin": "BTC", "oid": 67890}]
+
response = exchange.bulk_cancel(cancel_requests)
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -635,9 +589,10 @@ def test_bulk_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["cancels"][1]["a"] == 1
assert action["cancels"][1]["o"] == 67890
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_bulk_cancel_by_cloid(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test bulk_cancel_by_cloid method"""
# Setup
@@ -647,18 +602,16 @@ def test_bulk_cancel_by_cloid(mock_post_action, mock_timestamp, mock_sign, excha
exchange.info.name_to_asset = lambda x: 1
from hyperliquid.utils.types import Cloid
+
cloid1 = Cloid.from_str("0x00000000000000000000000000000001")
cloid2 = Cloid.from_str("0x00000000000000000000000000000002")
# Test multiple cancels by cloid
- cancel_requests = [
- {"coin": "ETH", "cloid": cloid1},
- {"coin": "BTC", "cloid": cloid2}
- ]
-
+ cancel_requests = [{"coin": "ETH", "cloid": cloid1}, {"coin": "BTC", "cloid": cloid2}]
+
response = exchange.bulk_cancel_by_cloid(cancel_requests)
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -670,9 +623,10 @@ def test_bulk_cancel_by_cloid(mock_post_action, mock_timestamp, mock_sign, excha
assert action["cancels"][1]["asset"] == 1
assert action["cancels"][1]["cloid"] == cloid2.to_raw()
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_schedule_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test schedule_cancel method"""
# Setup
@@ -682,45 +636,46 @@ def test_schedule_cancel(mock_post_action, mock_timestamp, mock_sign, exchange):
# Test 1: Basic schedule cancel without time (uses current timestamp)
response = exchange.schedule_cancel()
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
action = call_args[1]
assert action["type"] == "scheduleCancel"
assert "time" not in action # No specific time provided
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
-
+
# Test 2: Schedule cancel with specific time
mock_sign.reset_mock()
mock_post_action.reset_mock()
-
+
cancel_time = 1234567890 + 10000 # 10 seconds from now
response = exchange.schedule_cancel(cancel_time)
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
action = call_args[1]
assert action["type"] == "scheduleCancel"
assert action["time"] == cancel_time
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_update_leverage(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test update_leverage method"""
# Setup
@@ -731,9 +686,9 @@ def test_update_leverage(mock_post_action, mock_timestamp, mock_sign, exchange):
# Test 1: Update leverage with default cross margin
response = exchange.update_leverage(leverage=10, name="ETH")
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -742,24 +697,20 @@ def test_update_leverage(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["asset"] == 1
assert action["leverage"] == 10
assert action["isCross"] is True
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
-
+
# Test 2: Update leverage with isolated margin
mock_sign.reset_mock()
mock_post_action.reset_mock()
-
- response = exchange.update_leverage(
- leverage=5,
- name="BTC",
- is_cross=False
- )
-
+
+ response = exchange.update_leverage(leverage=5, name="BTC", is_cross=False)
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -768,15 +719,16 @@ def test_update_leverage(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["asset"] == 1
assert action["leverage"] == 5
assert action["isCross"] is False
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_update_isolated_margin(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test update_isolated_margin method"""
# Setup
@@ -786,13 +738,10 @@ def test_update_isolated_margin(mock_post_action, mock_timestamp, mock_sign, exc
exchange.info.name_to_asset = lambda x: 1
# Test: Update isolated margin
- response = exchange.update_isolated_margin(
- amount=1000.0,
- name="ETH"
- )
-
+ response = exchange.update_isolated_margin(amount=1000.0, name="ETH")
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -801,15 +750,16 @@ def test_update_isolated_margin(mock_post_action, mock_timestamp, mock_sign, exc
assert action["asset"] == 1
assert action["isBuy"] is True # isBuy is always True in the implementation
assert "ntli" in action # ntli (notional) should be present
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_set_referrer(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test set_referrer method"""
# Setup
@@ -819,24 +769,25 @@ def test_set_referrer(mock_post_action, mock_timestamp, mock_sign, exchange):
# Test setting referrer code
response = exchange.set_referrer("ASDFASDF")
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
action = call_args[1]
assert action["type"] == "setReferrer"
assert action["code"] == "ASDFASDF"
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_create_sub_account(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test create_sub_account method"""
# Setup
@@ -846,24 +797,25 @@ def test_create_sub_account(mock_post_action, mock_timestamp, mock_sign, exchang
# Test creating sub account
response = exchange.create_sub_account("example")
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
action = call_args[1]
assert action["type"] == "createSubAccount"
assert action["name"] == "example"
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
-@patch('hyperliquid.exchange.sign_usd_class_transfer_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_usd_class_transfer_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_usd_class_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test usd_class_transfer method"""
# Setup
@@ -873,9 +825,9 @@ def test_usd_class_transfer(mock_post_action, mock_timestamp, mock_sign, exchang
# Test 1: Basic transfer without vault address
response = exchange.usd_class_transfer(amount=1000.0, to_perp=True)
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_usd_class_transfer_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -884,16 +836,16 @@ def test_usd_class_transfer(mock_post_action, mock_timestamp, mock_sign, exchang
assert action["amount"] == "1000.0"
assert action["toPerp"] is True
assert action["nonce"] == 1234567890
-
+
# Test 2: Transfer with vault address
mock_sign.reset_mock()
mock_post_action.reset_mock()
exchange.vault_address = "0x1234"
-
+
response = exchange.usd_class_transfer(amount=500.5, to_perp=False)
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_usd_class_transfer_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -903,9 +855,10 @@ def test_usd_class_transfer(mock_post_action, mock_timestamp, mock_sign, exchang
assert action["toPerp"] is False
assert action["nonce"] == 1234567890
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_sub_account_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test sub_account_transfer method"""
# Setup
@@ -915,14 +868,10 @@ def test_sub_account_transfer(mock_post_action, mock_timestamp, mock_sign, excha
# Test deposit to sub account
sub_account = "0x1d9470d4b963f552e6f671a81619d395877bf409"
- response = exchange.sub_account_transfer(
- sub_account_user=sub_account,
- is_deposit=True,
- usd=1000
- )
-
+ response = exchange.sub_account_transfer(sub_account_user=sub_account, is_deposit=True, usd=1000)
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -931,19 +880,15 @@ def test_sub_account_transfer(mock_post_action, mock_timestamp, mock_sign, excha
assert action["subAccountUser"] == sub_account
assert action["isDeposit"] is True
assert action["usd"] == 1000
-
+
# Test withdrawal from sub account
mock_sign.reset_mock()
mock_post_action.reset_mock()
-
- response = exchange.sub_account_transfer(
- sub_account_user=sub_account,
- is_deposit=False,
- usd=500
- )
-
+
+ response = exchange.sub_account_transfer(sub_account_user=sub_account, is_deposit=False, usd=500)
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -953,9 +898,10 @@ def test_sub_account_transfer(mock_post_action, mock_timestamp, mock_sign, excha
assert action["isDeposit"] is False
assert action["usd"] == 500
-@patch('hyperliquid.exchange.sign_l1_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_l1_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_vault_usd_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test vault_usd_transfer method"""
# Setup
@@ -965,14 +911,10 @@ def test_vault_usd_transfer(mock_post_action, mock_timestamp, mock_sign, exchang
# Test vault transfer
vault_address = "0xa15099a30bbf2e68942d6f4c43d70d04faeab0a0"
- response = exchange.vault_usd_transfer(
- vault_address=vault_address,
- is_deposit=True,
- usd=5_000_000
- )
-
+ response = exchange.vault_usd_transfer(vault_address=vault_address, is_deposit=True, usd=5_000_000)
+
assert response == {"status": "ok"}
-
+
# Verify sign_l1_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -981,15 +923,16 @@ def test_vault_usd_transfer(mock_post_action, mock_timestamp, mock_sign, exchang
assert action["vaultAddress"] == vault_address
assert action["isDeposit"] is True
assert action["usd"] == 5_000_000
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature" # signature
-@patch('hyperliquid.exchange.sign_usd_transfer_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_usd_transfer_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_usd_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test usd_transfer method"""
# Setup
@@ -999,13 +942,10 @@ def test_usd_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
# Test USD transfer
destination = "0x5e9ee1089755c3435139848e47e6635505d5a13a"
- response = exchange.usd_transfer(
- destination=destination,
- amount=1000.0
- )
-
+ response = exchange.usd_transfer(destination=destination, amount=1000.0)
+
assert response == {"status": "ok"}
-
+
# Verify sign_usd_transfer_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -1013,7 +953,7 @@ def test_usd_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
assert message["destination"] == destination
assert message["amount"] == "1000.0"
assert message["time"] == 1234567890
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
@@ -1023,9 +963,10 @@ def test_usd_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["amount"] == "1000.0"
assert action["time"] == 1234567890
-@patch('hyperliquid.exchange.sign_spot_transfer_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_spot_transfer_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_spot_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test spot_transfer method"""
# Setup
@@ -1035,14 +976,10 @@ def test_spot_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
# Test spot transfer
destination = "0x5e9ee1089755c3435139848e47e6635505d5a13a"
- response = exchange.spot_transfer(
- amount=1000.0,
- destination=destination,
- token="ETH"
- )
-
+ response = exchange.spot_transfer(amount=1000.0, destination=destination, token="ETH")
+
assert response == {"status": "ok"}
-
+
# Verify sign_spot_transfer_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -1052,7 +989,7 @@ def test_spot_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
assert message["token"] == "ETH"
assert message["time"] == 1234567890
assert message["type"] == "spotSend"
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
@@ -1063,9 +1000,10 @@ def test_spot_transfer(mock_post_action, mock_timestamp, mock_sign, exchange):
assert action["token"] == "ETH"
assert action["time"] == 1234567890
-@patch('hyperliquid.exchange.sign_withdraw_from_bridge_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_withdraw_from_bridge_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_withdraw_from_bridge(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test withdraw_from_bridge method"""
# Setup
@@ -1075,13 +1013,10 @@ def test_withdraw_from_bridge(mock_post_action, mock_timestamp, mock_sign, excha
# Test bridge withdrawal
destination = "0x5e9ee1089755c3435139848e47e6635505d5a13a"
- response = exchange.withdraw_from_bridge(
- amount=1000.0,
- destination=destination
- )
-
+ response = exchange.withdraw_from_bridge(amount=1000.0, destination=destination)
+
assert response == {"status": "ok"}
-
+
# Verify sign_withdraw_from_bridge_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -1090,7 +1025,7 @@ def test_withdraw_from_bridge(mock_post_action, mock_timestamp, mock_sign, excha
assert action["destination"] == destination
assert action["amount"] == "1000.0"
assert action["time"] == 1234567890
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
@@ -1100,10 +1035,11 @@ def test_withdraw_from_bridge(mock_post_action, mock_timestamp, mock_sign, excha
assert action["amount"] == "1000.0"
assert action["time"] == 1234567890
-@patch('hyperliquid.exchange.sign_agent')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
-@patch('hyperliquid.exchange.secrets.token_hex')
+
+@patch("hyperliquid.exchange.sign_agent")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
+@patch("hyperliquid.exchange.secrets.token_hex")
def test_approve_agent(mock_token_hex, mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test approve_agent method"""
# Setup
@@ -1115,10 +1051,10 @@ def test_approve_agent(mock_token_hex, mock_post_action, mock_timestamp, mock_si
# Test 1: approve agent without name
response, agent_key = exchange.approve_agent()
-
+
assert response == {"status": "ok"}
assert agent_key == "0x" + ("a" * 64)
-
+
# Verify sign_agent was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -1127,16 +1063,16 @@ def test_approve_agent(mock_token_hex, mock_post_action, mock_timestamp, mock_si
assert "agentAddress" in action
assert action["nonce"] == 1234567890
assert "agentName" not in action
-
+
# Test 2: approve agent with name
mock_sign.reset_mock()
mock_post_action.reset_mock()
-
+
response, agent_key = exchange.approve_agent(name="test_agent")
-
+
assert response == {"status": "ok"}
assert agent_key == "0x" + ("a" * 64)
-
+
# Verify sign_agent was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -1146,9 +1082,10 @@ def test_approve_agent(mock_token_hex, mock_post_action, mock_timestamp, mock_si
assert action["nonce"] == 1234567890
assert action["agentName"] == "test_agent"
-@patch('hyperliquid.exchange.sign_approve_builder_fee')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_approve_builder_fee")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_approve_builder_fee(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test approve_builder_fee method"""
# Setup
@@ -1157,13 +1094,10 @@ def test_approve_builder_fee(mock_post_action, mock_timestamp, mock_sign, exchan
mock_post_action.return_value = {"status": "ok"}
# Test approving builder fee
- response = exchange.approve_builder_fee(
- builder="0x1234567890123456789012345678901234567890",
- max_fee_rate="0.001"
- )
-
+ response = exchange.approve_builder_fee(builder="0x1234567890123456789012345678901234567890", max_fee_rate="0.001")
+
assert response == {"status": "ok"}
-
+
# Verify sign_approve_builder_fee was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -1172,7 +1106,7 @@ def test_approve_builder_fee(mock_post_action, mock_timestamp, mock_sign, exchan
assert action["builder"] == "0x1234567890123456789012345678901234567890"
assert action["maxFeeRate"] == "0.001"
assert action["nonce"] == 1234567890
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
@@ -1182,9 +1116,10 @@ def test_approve_builder_fee(mock_post_action, mock_timestamp, mock_sign, exchan
assert action["maxFeeRate"] == "0.001"
assert action["nonce"] == 1234567890
-@patch('hyperliquid.exchange.sign_convert_to_multi_sig_user_action')
-@patch('hyperliquid.exchange.get_timestamp_ms')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_convert_to_multi_sig_user_action")
+@patch("hyperliquid.exchange.get_timestamp_ms")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_convert_to_multi_sig_user(mock_post_action, mock_timestamp, mock_sign, exchange):
"""Test convert_to_multi_sig_user method"""
# Setup
@@ -1195,56 +1130,57 @@ def test_convert_to_multi_sig_user(mock_post_action, mock_timestamp, mock_sign,
# Test converting to multi-sig user
authorized_users = [
"0x0000000000000000000000000000000000000001",
- "0x0000000000000000000000000000000000000000" # Note: Will be sorted
+ "0x0000000000000000000000000000000000000000", # Note: Will be sorted
]
threshold = 1
response = exchange.convert_to_multi_sig_user(authorized_users, threshold)
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_convert_to_multi_sig_user_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
action = call_args[1]
assert action["type"] == "convertToMultiSigUser"
assert action["nonce"] == 1234567890
-
+
# Verify signers JSON structure
signers = json.loads(action["signers"])
assert signers["threshold"] == 1
assert signers["authorizedUsers"] == sorted(authorized_users)
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]
assert call_args[1] == "test_signature"
-@patch('hyperliquid.exchange.sign_multi_sig_action')
-@patch('hyperliquid.exchange.Exchange._post_action')
+
+@patch("hyperliquid.exchange.sign_multi_sig_action")
+@patch("hyperliquid.exchange.Exchange._post_action")
def test_multi_sig(mock_post_action, mock_sign, exchange):
"""Test multi_sig method"""
# Setup
mock_sign.return_value = "test_signature"
mock_post_action.return_value = {"status": "ok"}
-
+
# Test parameters
multi_sig_user = "0xABCD"
inner_action = {"type": "order", "data": "test"}
signatures = ["sig1", "sig2"]
nonce = 1234567890
vault_address = "0x1234"
-
+
# Test multi-sig action
response = exchange.multi_sig(
multi_sig_user=multi_sig_user,
inner_action=inner_action,
signatures=signatures,
nonce=nonce,
- vault_address=vault_address
+ vault_address=vault_address,
)
-
+
assert response == {"status": "ok"}
-
+
# Verify sign_multi_sig_action was called correctly
mock_sign.assert_called_once()
call_args = mock_sign.call_args[0]
@@ -1255,7 +1191,7 @@ def test_multi_sig(mock_post_action, mock_sign, exchange):
assert action["payload"]["multiSigUser"] == multi_sig_user.lower()
assert action["payload"]["outerSigner"] == exchange.wallet.address.lower()
assert action["payload"]["action"] == inner_action
-
+
# Verify _post_action was called correctly
mock_post_action.assert_called_once()
call_args = mock_post_action.call_args[0]