diff --git a/CHANGELOG.md b/CHANGELOG.md index 9560e84..5739f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [1.2.2] - 2024-APR-9 + +### Added +- Support for ClosePosition endpoint + +### Changed +- Audience no longer included in JWT generation + ## [1.2.1] - 2024-MAR-27 ### Added diff --git a/coinbase/__version__.py b/coinbase/__version__.py index a955fda..bc86c94 100644 --- a/coinbase/__version__.py +++ b/coinbase/__version__.py @@ -1 +1 @@ -__version__ = "1.2.1" +__version__ = "1.2.2" diff --git a/coinbase/jwt_generator.py b/coinbase/jwt_generator.py index 7ede411..a942c65 100644 --- a/coinbase/jwt_generator.py +++ b/coinbase/jwt_generator.py @@ -7,7 +7,7 @@ from coinbase.constants import BASE_URL, REST_SERVICE, WS_SERVICE -def build_jwt(key_var, secret_var, service, uri=None) -> str: +def build_jwt(key_var, secret_var, uri=None) -> str: """ :meta private: """ @@ -28,7 +28,6 @@ def build_jwt(key_var, secret_var, service, uri=None) -> str: "iss": "coinbase-cloud", "nbf": int(time.time()), "exp": int(time.time()) + 120, - "aud": [service], } if uri: @@ -61,7 +60,7 @@ def build_rest_jwt(uri, key_var, secret_var) -> str: - **key_var (str)** - The API key - **secret_var (str)** - The API key secret """ - return build_jwt(key_var, secret_var, REST_SERVICE, uri=uri) + return build_jwt(key_var, secret_var, uri=uri) def build_ws_jwt(key_var, secret_var) -> str: @@ -80,7 +79,7 @@ def build_ws_jwt(key_var, secret_var) -> str: - **key_var (str)** - The API key - **secret_var (str)** - The API key secret """ - return build_jwt(key_var, secret_var, WS_SERVICE) + return build_jwt(key_var, secret_var) def format_jwt_uri(method, path) -> str: diff --git a/coinbase/rest/__init__.py b/coinbase/rest/__init__.py index eb41bfe..7eab230 100755 --- a/coinbase/rest/__init__.py +++ b/coinbase/rest/__init__.py @@ -8,6 +8,7 @@ class RESTClient(RESTBase): from .fees import get_transaction_summary from .futures import ( cancel_pending_futures_sweep, + close_position, get_futures_balance_summary, get_futures_position, list_futures_positions, diff --git a/coinbase/rest/futures.py b/coinbase/rest/futures.py index 904fa16..7f0ad7d 100644 --- a/coinbase/rest/futures.py +++ b/coinbase/rest/futures.py @@ -1,8 +1,34 @@ -from typing import Any, Dict +from typing import Any, Dict, Optional from coinbase.constants import API_PREFIX +def close_position( + self, client_order_id: str, product_id: str, size: Optional[str] = None, **kwargs +) -> Dict[str, Any]: + """ + **Close Position** + _________________ + + [POST] https://api.coinbase.com/api/v3/brokerage/orders/close_position + + __________ + + **Description:** + + Places an order to close any open positions for a specified ``product_id``. + + __________ + + **Read more on the official documentation:** `Close Position + `_ + """ + endpoint = f"{API_PREFIX}/orders/close_position" + data = {"client_order_id": client_order_id, "product_id": product_id, "size": size} + + return self.post(endpoint, data=data, **kwargs) + + def get_futures_balance_summary(self, **kwargs) -> Dict[str, Any]: """ **Get Futures Balance Summary** diff --git a/tests/rest/test_futures.py b/tests/rest/test_futures.py index 733cb95..f920ceb 100644 --- a/tests/rest/test_futures.py +++ b/tests/rest/test_futures.py @@ -8,6 +8,38 @@ class FuturesTest(unittest.TestCase): + def test_close_position(self): + client = RESTClient(TEST_API_KEY, TEST_API_SECRET) + + expected_response = { + "client_order_id": "client_order_id_1", + "product_id": "product_id_1", + } + + with Mocker() as m: + m.request( + "POST", + "https://api.coinbase.com/api/v3/brokerage/orders/close_position", + json=expected_response, + ) + closedOrder = client.close_position( + "client_order_id_1", "product_id_1", "100" + ) + + captured_request = m.request_history[0] + captured_json = captured_request.json() + + self.assertEqual(captured_request.query, "") + self.assertEqual( + captured_json, + { + "client_order_id": "client_order_id_1", + "product_id": "product_id_1", + "size": "100", + }, + ) + self.assertEqual(closedOrder, expected_response) + def test_get_futures_balance_summary(self): client = RESTClient(TEST_API_KEY, TEST_API_SECRET) diff --git a/tests/test_jwt_generator.py b/tests/test_jwt_generator.py index 3e9e6ac..a235e95 100644 --- a/tests/test_jwt_generator.py +++ b/tests/test_jwt_generator.py @@ -15,30 +15,24 @@ def test_build_rest_jwt(self): uri = jwt_generator.format_jwt_uri("GET", "/api/v3/brokerage/accounts") result_jwt = jwt_generator.build_rest_jwt(uri, TEST_API_KEY, TEST_API_SECRET) - decoded_data = jwt.decode( - result_jwt, TEST_API_SECRET, algorithms=["ES256"], audience=[REST_SERVICE] - ) + decoded_data = jwt.decode(result_jwt, TEST_API_SECRET, algorithms=["ES256"]) header_bytes = base64.urlsafe_b64decode(str(result_jwt.split(".")[0] + "==")) decoded_header = json.loads(header_bytes.decode("utf-8")) self.assertEqual(decoded_data["sub"], TEST_API_KEY) self.assertEqual(decoded_data["iss"], "coinbase-cloud") - self.assertEqual(decoded_data["aud"], [REST_SERVICE]) self.assertEqual(decoded_data["uri"], uri) self.assertEqual(decoded_header["kid"], TEST_API_KEY) def test_build_ws_jwt(self): result_jwt = jwt_generator.build_ws_jwt(TEST_API_KEY, TEST_API_SECRET) - decoded_data = jwt.decode( - result_jwt, TEST_API_SECRET, algorithms=["ES256"], audience=[WS_SERVICE] - ) + decoded_data = jwt.decode(result_jwt, TEST_API_SECRET, algorithms=["ES256"]) header_bytes = base64.urlsafe_b64decode(str(result_jwt.split(".")[0] + "==")) decoded_header = json.loads(header_bytes.decode("utf-8")) self.assertEqual(decoded_data["sub"], TEST_API_KEY) self.assertEqual(decoded_data["iss"], "coinbase-cloud") - self.assertEqual(decoded_data["aud"], [WS_SERVICE]) self.assertNotIn("uri", decoded_data) self.assertEqual(decoded_header["kid"], TEST_API_KEY)