From 9e8faabda766d3ebbc313e601d5da739c7b340ca Mon Sep 17 00:00:00 2001 From: Memo Alaloush Date: Wed, 9 Aug 2023 12:54:17 +0200 Subject: [PATCH 01/12] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd22fed --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# bluefin-client-python-sui +Python Client for Bluefin Exchange API and Smart Contracts for SUI From 586a4244af9e1c8cf1df1b2eef56d16de63bbdfa Mon Sep 17 00:00:00 2001 From: Terraform User Date: Wed, 9 Aug 2023 12:56:44 +0200 Subject: [PATCH 02/12] Managed by Terraform --- CODEOWNERS | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..18efe04 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,4 @@ +* @fireflyprotocol/eng-quant +environment-stack/* @fireflyprotocol/eng-platform +.github/* @fireflyprotocol/eng-platform + From 95124dd06a2ceafa8b0359a2ac8d2704bdd25e9c Mon Sep 17 00:00:00 2001 From: Ibad Ur Rahman Date: Tue, 22 Aug 2023 20:09:39 +0500 Subject: [PATCH 03/12] Quant 852 (#1) * first commit copy paste code * initialisation and onboarding signature done * order signign, onboarding and partial cancel * running all cancel mode * changing names * changin names * adding publish * editing readme * adding readme * Update publish_to_pypy.yml * Update README.md * adding workflow * changing workflow file * fixing cancel order * removing scoket * fixing leverage * adding record of changes --- .github/workflows/publish_to_pypy.yml | 31 + CHANGES.md | 52 + LICENSE | 21 + README.md | 264 +- examples/1.initialization.py | 34 + examples/10.1.socket_readonly.py | 86 + examples/10.sockets.py | 81 + examples/11.sub_accounts.py | 51 + examples/12.open_order_event.py | 96 + examples/13.orderbook_updates.py | 85 + examples/14.web_sockets.py | 62 + examples/15.get_funding_history.py | 33 + .../16.listening_events_using_sub_account.py | 82 + examples/17.1.get_orders_readonly.py | 69 + examples/17.get_orders.py | 61 + examples/18.dms_api.py | 55 + examples/19.Generate_readonly_token.py | 37 + examples/2.user_info.py | 42 + examples/3.balance.py | 53 + examples/4.placing_orders.py | 106 + examples/5.adjusting_leverage.py | 39 + examples/6.adjusting_margin.py | 48 + examples/7.cancelling_orders.py | 84 + examples/8.exchange_data.py | 66 + examples/9.user_data.py | 69 + examples/SocketClient.py | 22 + examples/SocketServer.py | 42 + examples/config.py | 3 + pyproject.toml | 25 + requirements.in | 49 + res/banner.png | Bin 0 -> 23051 bytes src/__init__.py | 0 src/bluefin_client_sui/__init__.py | 7 + src/bluefin_client_sui/account.py | 38 + src/bluefin_client_sui/api_service.py | 117 + src/bluefin_client_sui/client.py | 964 ++++++++ src/bluefin_client_sui/constants.py | 137 ++ src/bluefin_client_sui/contract_abis.py | 2116 +++++++++++++++++ src/bluefin_client_sui/contracts.py | 100 + src/bluefin_client_sui/enumerations.py | 105 + src/bluefin_client_sui/interfaces.py | 223 ++ src/bluefin_client_sui/onboarding_signer.py | 29 + src/bluefin_client_sui/order_signer.py | 111 + src/bluefin_client_sui/signer.py | 45 + src/bluefin_client_sui/socket_manager.py | 103 + src/bluefin_client_sui/sockets_lib.py | 194 ++ src/bluefin_client_sui/utilities.py | 120 + src/bluefin_client_sui/websocket_client.py | 182 ++ src/check.py | 31 + 49 files changed, 6468 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/publish_to_pypy.yml create mode 100644 CHANGES.md create mode 100644 LICENSE create mode 100644 examples/1.initialization.py create mode 100644 examples/10.1.socket_readonly.py create mode 100644 examples/10.sockets.py create mode 100644 examples/11.sub_accounts.py create mode 100644 examples/12.open_order_event.py create mode 100644 examples/13.orderbook_updates.py create mode 100644 examples/14.web_sockets.py create mode 100644 examples/15.get_funding_history.py create mode 100644 examples/16.listening_events_using_sub_account.py create mode 100644 examples/17.1.get_orders_readonly.py create mode 100644 examples/17.get_orders.py create mode 100644 examples/18.dms_api.py create mode 100644 examples/19.Generate_readonly_token.py create mode 100644 examples/2.user_info.py create mode 100644 examples/3.balance.py create mode 100644 examples/4.placing_orders.py create mode 100644 examples/5.adjusting_leverage.py create mode 100644 examples/6.adjusting_margin.py create mode 100644 examples/7.cancelling_orders.py create mode 100644 examples/8.exchange_data.py create mode 100644 examples/9.user_data.py create mode 100644 examples/SocketClient.py create mode 100644 examples/SocketServer.py create mode 100644 examples/config.py create mode 100644 pyproject.toml create mode 100644 requirements.in create mode 100644 res/banner.png create mode 100644 src/__init__.py create mode 100644 src/bluefin_client_sui/__init__.py create mode 100644 src/bluefin_client_sui/account.py create mode 100644 src/bluefin_client_sui/api_service.py create mode 100644 src/bluefin_client_sui/client.py create mode 100644 src/bluefin_client_sui/constants.py create mode 100644 src/bluefin_client_sui/contract_abis.py create mode 100644 src/bluefin_client_sui/contracts.py create mode 100644 src/bluefin_client_sui/enumerations.py create mode 100644 src/bluefin_client_sui/interfaces.py create mode 100644 src/bluefin_client_sui/onboarding_signer.py create mode 100644 src/bluefin_client_sui/order_signer.py create mode 100644 src/bluefin_client_sui/signer.py create mode 100644 src/bluefin_client_sui/socket_manager.py create mode 100644 src/bluefin_client_sui/sockets_lib.py create mode 100644 src/bluefin_client_sui/utilities.py create mode 100644 src/bluefin_client_sui/websocket_client.py create mode 100644 src/check.py diff --git a/.github/workflows/publish_to_pypy.yml b/.github/workflows/publish_to_pypy.yml new file mode 100644 index 0000000..d1b9ab1 --- /dev/null +++ b/.github/workflows/publish_to_pypy.yml @@ -0,0 +1,31 @@ +--- +name: publish_to_pypi +on: + push: + branches: + - quant-852 + pull_request: + branches: + - main + types: + - closed +jobs: + build-n-publish: + #if: github.event.pull_request.merged == true + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install pypa/build + run: python -m pip install build --user + - name: Build a binary wheel and a source tarball + run: python -m build --sdist --wheel --outdir dist/ . + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_KEY }} + skip_existing: true diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..ac72d4b --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,52 @@ +# Changes w.r.t old library +## Initialisation change +1. The way we initialise is still the same, the difference is that NOW we give `SEED_PHRASE` as `TEST_ACCT_KEY` +2. Currently we support test phrase only and we sign it using ED25519 + + +## Placing/Cancelling Orders +1. Now while placing orders we need to ensure that price, quantity and leverage are sent in sui format. we have exposed a function `toSuiBase(value)` to convert our value to that format. +2. Same goes for cancelling orders. + +## Sockets +1. Nothing is changes in sockets except for the url link + +## Signing (internal) +1. We do not sign our transaction using ETH library anymore. We now use `nacl` and `hashlib` in order to sign the messages. +2. For signing orders we sign it using the following scheme + 1. Assuming we have the order with values in sui format. + 2. We first get the `orderhash` of the order using the following method + ```python flags = self.get_order_flags(order) + flags = hexToByteArray(numberToHex(flags,2)) + + buffer=bytearray() + orderPriceHex=hexToByteArray(numberToHex(int(order["price"]))) + orderQuantityHex=hexToByteArray(numberToHex(int(order['quantity']))) + orderLeverageHex=hexToByteArray (numberToHex(int(order['leverage']))) + orderSalt=hexToByteArray(numberToHex(int(order['salt']))) + orderExpiration=hexToByteArray(numberToHex(int(order['expiration']),16)) + orderMaker=hexToByteArray(numberToHex(int(order['maker'],16),64)) + orderMarket=hexToByteArray(numberToHex(int(order['market'],16),64)) + bluefin=bytearray("Bluefin", encoding="utf-8") + + buffer=orderPriceHex+orderQuantityHex+orderLeverageHex+orderSalt+orderExpiration+orderMaker+orderMarket+flags+bluefin + ``` + 3. We then get the sha256 of the buffer.hex() + 4. We then sign it and append '1' to it, specifying that we signed it using ed25519 +3. For signing onboarding signer we follow a different approach. + 1. What is onboarding signer: When we are calling a firefly init function we sign a string using our private key and send it to bluefin exchange along with our public key, bluefin exchange verifies it and returns us a token. + 2. For signing it we first convert our message to bytes and then add [3,0,0, len(message)] to the start of our bytearray and then our message. if our message length is greater than 256 then it wont fit in a byte in this case we follow BCS methodology to send our message. + +4. For signing cancel order, there are two ways. + 1. We first sign the order and send it to bluefin. Now we have the hash of the order. We first change our encode our hash to BCS format. + ```python + sigDict={} + sigDict['orderHashes']=order_hash + encodedMessage=self.encode_message(sigDict) + ``` + 2. Please have a look at signer.py to see the implementation or encode_message. + 3. Then we sign the encodedMessage and send the signature to bluefin for cancelling order +5. For signing cancel order second method. + 1. We sign the order and send it to bluefin, now imagine we do not have the hash of our order. + 2. We resign our order and get the hash and then we follow the similar approach as above. Please have a look at `7.cancelling_orders.py` in our examples file. + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f419762 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [2022] [Seed Labs] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index cd22fed..ba51960 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,262 @@ -# bluefin-client-python-sui -Python Client for Bluefin Exchange API and Smart Contracts for SUI +# Firefly Client Library + +[Firefly logo](#) + +
+ +![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/fireflyprotocol/bluefin-client-python-sui/publish_to_pypi.yml) +[![pypi version](https://img.shields.io/pypi/v/bluefun_client_sui?logo=pypi)](https://pypi.org/project/bluefin_client_sui/) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +
+ +Python Client for the Firefly Exchange API and Smart Contracts for SUI. +​ + +### Install + +The package can be installed from [PyPi](https://pypi.org/project/firefly-exchange-client/) using pip: + +``` +pip install bluefin_client_sui +``` + +Alternatively, you could run: + +``` +pip install . +``` + +The package currently supports python `>=3.8`. Find complete documentation on the library at https://docs.firefly.exchange/. + +### Getting Started + +When initializing the client, users must accept [terms and conditions](https://firefly.exchange/terms-of-use) and define network object containing the following values: + +```json +{ + "apiGateway":"https://dapi.api.sui-staging.bluefin.io", + "socketURL":"wss://dapi.api.sui-staging.bluefin.io", + "dmsURL":"https://dapi.api.sui-staging.bluefin.io", + "webSocketURL":"wss://notifications.api.sui-staging.bluefin.io", + "onboardingUrl": "https://testnet.bluefin.io" +} +``` + +Users can import predefined networks from [constants](https://github.com/fireflyprotocol/firefly_exchange_client/blob/main/src/firefly_exchange_client/constants.py): + +```python +from bluefin_client_sui import Networks +``` + +For testing purposes use `Networks[SUI_STAGING]` and for production please use `Networks[SUI_PROD]`. Currently only SUI_STAGING is available. + +## Initialization example​ + +```python +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks +from pprint import pprint +import asyncio + +async def main(): + # initialize client + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + # initialize the client + # on boards user on firefly. Must be set to true for first time use + await client.init(True) + + print('Account Address:', client.get_public_address()) + + # # gets user account data on-chain + data = await client.get_user_account_data() + + # close aio http connection + await client.apis.close_session() + + pprint(data) + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() +``` + +**Read-only Initialization:** +Firefly-client can also be initialized in `read-only` mode, below is the example: +```python +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks +from pprint import pprint +import asyncio + +async def main(): + # initialize client without providing private_key + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + ) + + # Initializing client for the private key provided. The second argument api_token is optional + await client.init(True,"54b0bfafc9a48728f76e52848a716e96d490263392e3959c2d44f05dea960761") + + # close aio http connection + await client.apis.close_session() + await client.dmsApi.close_session() + + pprint(data) + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() +``` +​Here is the [list](https://docs.bluefin.io/8/2.readonly-access-data) of APIs that can be accessed in `read-only` mode. + +**Placing Orders:** + +```python +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +from bluefin_client_sui.utilities import toSuiBase +import asyncio + +async def main(): + # initialize client + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + await client.init(True) + + # add market that you wish to trade on ETH/BTC are supported currently + client.add_market(MARKET_SYMBOLS.ETH) + + user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) + + # creates a LIMIT order to be signed + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # market symbol + price=toSuiBase(1900), # price at which you want to place order + quantity=toSuiBase(0.01), # quantity + side=ORDER_SIDE.SELL, + orderType=ORDER_TYPE.LIMIT, + leverage=toSuiBase(user_leverage) + ) + + # create signed order + signed_order = client.create_signed_order(signature_request) + + print("Placing a limit order") + # place signed order on orderbook + resp = await client.post_signed_order(signed_order) + + # returned order with PENDING state + print(resp) + + await client.apis.close_session() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() +``` + +​ +**Listening To Events Using Socket.io:** + +```python +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, SOCKET_EVENTS +import asyncio +import time + +def callback(event): + print("Event data:", event) + +async def main(): + # initialize + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + await client.init(True) + # make connection with firefly exchange + await client.socket.open() + + # subscribe to local user events + await client.socket.subscribe_user_update_by_token() + + # listen to exchange health updates and trigger callback + await client.socket.listen(SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback) + time.sleep(10) + # unsubscribe from user events + await client.socket.unsubscribe_user_update_by_token() + # close socket connection + await client.socket.close() + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close()​ +``` + +Look at the [example](https://github.com/fireflyprotocol/firefly_exchange_client/tree/main/examples) directory to see more examples on how to use this library. + +**Listening To Events Using Web Sockets:** + +```python +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, SOCKET_EVENTS, MARKET_SYMBOLS +import time +import asyncio + +def callback(event): + print("Event data:", event) + +async def main(): + # initialize + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + await client.init(True) + + def on_open(ws): + # subscribe to global events + resp = client.webSocketClient.subscribe_global_updates_by_symbol(symbol=MARKET_SYMBOLS.ETH) + if resp: + print("Subscribed to global updates") + resp = client.webSocketClient.subscribe_user_update_by_token() + if resp: + print("Subscribed to user updates") + + # make connection with firefly exchange + client.webSocketClient.initialize_socket(on_open=on_open) + # listen to user order updates and trigger callback + client.webSocketClient.listen(SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback) + + time.sleep(60) + + # unsubscribe from global events + client.webSocketClient.unsubscribe_global_updates_by_symbol(symbol=MARKET_SYMBOLS.ETH) + client.webSocketClient.stop() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() +``` + diff --git a/examples/1.initialization.py b/examples/1.initialization.py new file mode 100644 index 0000000..5fc4004 --- /dev/null +++ b/examples/1.initialization.py @@ -0,0 +1,34 @@ +from config import TEST_ACCT_KEY, TEST_NETWORK +import os +import sys +print (os.getcwd()) +sys.path.append(os.getcwd()+"/src/") + +from bluefin_client_sui import FireflyClient, Networks +from pprint import pprint +import asyncio + +async def main(): + # initialize client + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + # Initializing client for the private key provided. The second argument api_token is optional + await client.init(True) + + print('Account Address:', client.get_public_address()) + + # # gets user account data on-chain + data = await client.get_user_account_data() + + await client.close_connections() + + pprint(data) + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/10.1.socket_readonly.py b/examples/10.1.socket_readonly.py new file mode 100644 index 0000000..3c27096 --- /dev/null +++ b/examples/10.1.socket_readonly.py @@ -0,0 +1,86 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") + +import time +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS +import asyncio + +TEST_NETWORK="SUI_STAGING" + +event_received = False + +def callback(event): + global event_received + print("Event data:", event) + event_received = True + + +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + response = await client.generate_readonly_token() + readOnlyclient = FireflyClient(True, Networks[TEST_NETWORK]) + await readOnlyclient.init(True,response) + + + async def my_callback(): + print("Subscribing To Rooms") + # subscribe to global event updates for BTC market + status = await readOnlyclient.socket.subscribe_global_updates_by_symbol(MARKET_SYMBOLS.BTC) + print("Subscribed to global BTC events: {}".format(status)) + + # subscribe to local user events + status = await readOnlyclient.socket.subscribe_user_update_by_token() + print("Subscribed to user events: {}".format(status)) + + # triggered when order book updates + print("Listening to exchange health updates") + await readOnlyclient.socket.listen(SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback) + + # triggered when status of any user order updates + print("Listening to user order updates") + await readOnlyclient.socket.listen(SOCKET_EVENTS.ORDER_UPDATE.value, callback) + + + + await readOnlyclient.socket.listen("connect",my_callback) + + + + # must open socket before subscribing + print("Making socket connection to firefly exchange") + await readOnlyclient.socket.open() + + + # SOCKET_EVENTS contains all events that can be listened to + + # logs event name and data for all markets and users that are subscribed. + # helpful for debugging + # client.socket.listen("default",callback) + timeout = 30 + end_time = time.time() + timeout + while not event_received and time.time() < end_time: + time.sleep(1) + + # # unsubscribe from global events + status = await readOnlyclient.socket.unsubscribe_global_updates_by_symbol(MARKET_SYMBOLS.BTC) + print("Unsubscribed from global BTC events: {}".format(status)) + + status = await readOnlyclient.socket.unsubscribe_user_update_by_token() + print("Unsubscribed from user events: {}".format(status)) + + + # # close socket connection + print("Closing sockets!") + await readOnlyclient.socket.close() + + await readOnlyclient.apis.close_session() + + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/10.sockets.py b/examples/10.sockets.py new file mode 100644 index 0000000..6ccfb1e --- /dev/null +++ b/examples/10.sockets.py @@ -0,0 +1,81 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") + +import time +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS +import asyncio +TEST_NETWORK="SUI_STAGING" +event_received = False + +def callback(event): + global event_received + print("Event data:", event) + event_received = True + + +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + + async def my_callback(): + print("Subscribing To Rooms") + # subscribe to global event updates for BTC market + status = await client.socket.subscribe_global_updates_by_symbol(MARKET_SYMBOLS.BTC) + print("Subscribed to global BTC events: {}".format(status)) + + # subscribe to local user events + status = await client.socket.subscribe_user_update_by_token() + print("Subscribed to user events: {}".format(status)) + + # triggered when order book updates + print("Listening to exchange health updates") + await client.socket.listen(SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback) + + # triggered when status of any user order updates + print("Listening to user order updates") + await client.socket.listen(SOCKET_EVENTS.ORDER_UPDATE.value, callback) + + + + await client.socket.listen("connect",my_callback) + + + + # must open socket before subscribing + print("Making socket connection to firefly exchange") + await client.socket.open() + + + # SOCKET_EVENTS contains all events that can be listened to + + # logs event name and data for all markets and users that are subscribed. + # helpful for debugging + # client.socket.listen("default",callback) + timeout = 30 + end_time = time.time() + timeout + while not event_received and time.time() < end_time: + time.sleep(1) + + # # unsubscribe from global events + status = await client.socket.unsubscribe_global_updates_by_symbol(MARKET_SYMBOLS.BTC) + print("Unsubscribed from global BTC events: {}".format(status)) + + status = await client.socket.unsubscribe_user_update_by_token() + print("Unsubscribed from user events: {}".format(status)) + + + # # close socket connection + print("Closing sockets!") + await client.socket.close() + + await client.apis.close_session() + + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/11.sub_accounts.py b/examples/11.sub_accounts.py new file mode 100644 index 0000000..651bc72 --- /dev/null +++ b/examples/11.sub_accounts.py @@ -0,0 +1,51 @@ +from config import TEST_ACCT_KEY, TEST_SUB_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, Networks, OrderSignatureRequest +import asyncio + + +async def main(): + + clientParent = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await clientParent.init(True) + + clientChild = FireflyClient(True, Networks[TEST_NETWORK], TEST_SUB_ACCT_KEY) + await clientChild.init(True) + + print("Parent: ", clientParent.get_public_address()) + + print("Child: ", clientChild.get_public_address()) + + # # whitelist sub account + status = await clientParent.update_sub_account(MARKET_SYMBOLS.ETH, clientChild.get_public_address(), True) + print("Sub account created: {}".format(status)) + + + clientChild.add_market(MARKET_SYMBOLS.ETH) + + parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) + + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # sub account is only whitelisted for ETH market + maker=clientParent.get_public_address(), # maker of the order is the parent account + price=0, + quantity=0.02, + side=ORDER_SIDE.BUY, + orderType=ORDER_TYPE.MARKET, + leverage=parent_leverage, + ) + + # order is signed using sub account's private key + signed_order = clientChild.create_signed_order(signature_request) + + resp = await clientChild.post_signed_order(signed_order) + + print(resp) + + await clientChild.close_connections() + await clientParent.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/12.open_order_event.py b/examples/12.open_order_event.py new file mode 100644 index 0000000..75f07d2 --- /dev/null +++ b/examples/12.open_order_event.py @@ -0,0 +1,96 @@ +''' +The code example opens socket connection and listens to user order update events +It places a limit order and as soon as its OPENED on order book, we receive +an event, log its data and terminate connection +''' +import sys,os +sys.path.append(os.getcwd()+"/src/") +import time +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +import asyncio +TEST_NETWORK="SUI_STAGING" + +event_received = False + + + + +async def place_limit_order(client:FireflyClient): + + # default leverage of account is set to 3 on firefly + await client.adjust_leverage(MARKET_SYMBOLS.ETH,1) + user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) + + + # creates a LIMIT order to be signed + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # market symbol + price=1300000000000, # price at which you want to place order + quantity=10000000, # quantity + side=ORDER_SIDE.SELL, + orderType=ORDER_TYPE.LIMIT, + leverage=1000000000 + ) + + # create signed order + signed_order = client.create_signed_order(signature_request) + + print("Placing a limit order") + # place signed order on orderbook + resp = await client.post_signed_order(signed_order) + + # returned order with PENDING state + print(resp) + + return + + +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + client.add_market(MARKET_SYMBOLS.ETH) + + def callback(event): + global event_received + print("Event data:", event) + event_received = True + + + # must open socket before subscribing + print("Making socket connection to firefly exchange") + await client.socket.open() + + # subscribe to user events + await client.socket.subscribe_user_update_by_token() + print("Subscribed to user events") + + print("Listening to user order updates") + await client.socket.listen(SOCKET_EVENTS.ORDER_UPDATE.value, callback) + + # place a limit order + await place_limit_order(client) + + timeout = 30 + end_time = time.time() + timeout + while not event_received and time.time() < end_time: + time.sleep(1) + + status = await client.socket.unsubscribe_user_update_by_token() + print("Unsubscribed from user events: {}".format(status)) + + # close socket connection + print("Closing sockets!") + await client.socket.close() + + await client.close_connections() + + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() + diff --git a/examples/13.orderbook_updates.py b/examples/13.orderbook_updates.py new file mode 100644 index 0000000..5f6acfd --- /dev/null +++ b/examples/13.orderbook_updates.py @@ -0,0 +1,85 @@ +''' +When ever the state of orderbook changes, an event is emitted by exchange. +In this code example we open a socket connection and listen to orderbook update event +''' +import sys,os +sys.path.append(os.getcwd()+"/src/") +import time +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +from bluefin_client_sui.utilities import toSuiBase +import asyncio +TEST_NETWORK="SUI_STAGING" + +event_received = False + +async def place_limit_order(client:FireflyClient): + + # default leverage of account is set to 3 on firefly + user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) + + # creates a LIMIT order to be signed + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # market symbol + price=toSuiBase(1300), # price at which you want to place order + quantity=toSuiBase(0.01), # quantity + side=ORDER_SIDE.SELL, + orderType=ORDER_TYPE.LIMIT, + leverage= toSuiBase(1) + ) + # create signed order + signed_order = client.create_signed_order(signature_request) + + print("Placing a limit order") + # place signed order on orderbook + resp = await client.post_signed_order(signed_order) + + # returned order with PENDING state + print(resp) + return + +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + client.add_market(MARKET_SYMBOLS.ETH) + + def callback(event): + global event_received + print("Event data:", event) + event_received = True + + + # must open socket before subscribing + print("Making socket connection to firefly exchange") + await client.socket.open() + + # subscribe to global event updates for ETH market + await client.socket.subscribe_global_updates_by_symbol(MARKET_SYMBOLS.ETH) + print("Subscribed to ETH Market events") + + print("Listening to ETH Orderbook update event") + await client.socket.listen(SOCKET_EVENTS.ORDERBOOK_UPDATE.value, callback) + + await place_limit_order(client) + + timeout = 30 + end_time = time.time() + timeout + while not event_received and time.time() < end_time: + time.sleep(1) + status = await client.socket.unsubscribe_global_updates_by_symbol(MARKET_SYMBOLS.ETH) + print("Unsubscribed from orderbook update events for ETH Market: {}".format(status)) + + # close socket connection + print("Closing sockets!") + await client.socket.close() + + await client.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() + diff --git a/examples/14.web_sockets.py b/examples/14.web_sockets.py new file mode 100644 index 0000000..de2060b --- /dev/null +++ b/examples/14.web_sockets.py @@ -0,0 +1,62 @@ + +import sys,os +sys.path.append(os.getcwd()+"/src/") +import time +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS, config_logging +import asyncio +import logging + +config_logging(logging, logging.DEBUG) + +event_received = False + +def callback(event): + global event_received + print("Event data:", event) + event_received = True + +async def main(): + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + def on_error(ws, error): + print(error) + + def on_close(ws): + # unsubscribe from global events + status = client.webSocketClient.unsubscribe_global_updates_by_symbol(MARKET_SYMBOLS.ETH) + print("Unsubscribed from global ETH events: {}".format(status)) + # close socket connection + print("### closed ###") + + def on_open(ws): + # subscribe to global event updates for ETH market + status = client.webSocketClient.subscribe_global_updates_by_symbol(MARKET_SYMBOLS.ETH) + print("Subscribed to global ETH events: {}".format(status)) + + # SOCKET_EVENTS contains all events that can be listened to + print("Listening to Exchange Health updates") + client.webSocketClient.listen(SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback) + + + # logs event name and data for all markets and users that are subscribed. + # helpful for debugging + # client.socket.listen("default",callback) + + + print("Making socket connection to firefly exchange") + client.webSocketClient.initialize_socket(on_open=on_open, on_error=on_error,on_close=on_close) + + timeout = 30 + end_time = time.time() + timeout + while not event_received and time.time() < end_time: + time.sleep(1) + + client.webSocketClient.stop() + await client.close_connections() + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/15.get_funding_history.py b/examples/15.get_funding_history.py new file mode 100644 index 0000000..5e41bb6 --- /dev/null +++ b/examples/15.get_funding_history.py @@ -0,0 +1,33 @@ + +import sys,os +sys.path.append(os.getcwd()+"/src/") +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, GetFundingHistoryRequest +from pprint import pprint +import asyncio + +async def main(): + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + + # create a funding history request + funding_history_request = GetFundingHistoryRequest( + symbol=MARKET_SYMBOLS.ETH, # market symbol + pageSize=50, # gets provided number of payments <= 50 + cursor=0 # fetch a particular page. A single page contains upto 50 records + ) + + # submit request for funding history + funding_history_response = await client.get_funding_history(funding_history_request) + + # returns funding history response + pprint(funding_history_response) + + await client.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/16.listening_events_using_sub_account.py b/examples/16.listening_events_using_sub_account.py new file mode 100644 index 0000000..6c87c48 --- /dev/null +++ b/examples/16.listening_events_using_sub_account.py @@ -0,0 +1,82 @@ +''' + This example shows how you can listen to user events using sub account + On our exchange, a sub account is trading on its parent's position and thus + has no position of its own. So when placing orders or listening to position updates + the sub account must specify the parent address whose position its listening. +''' +import time, sys +from config import TEST_ACCT_KEY, TEST_NETWORK, TEST_SUB_ACCT_KEY +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, OrderSignatureRequest, ORDER_SIDE, ORDER_TYPE, SOCKET_EVENTS +import asyncio + +def callback(event): + print("Event data: {}\n".format(event)) + +async def main(): + + clientParent = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await clientParent.init(True) + + clientChild = FireflyClient(True, Networks[TEST_NETWORK], TEST_SUB_ACCT_KEY) + await clientChild.init(True) + + print("Parent: ", clientParent.get_public_address()) + + print("Child: ", clientChild.get_public_address()) + + # # whitelist sub account + status = await clientParent.update_sub_account(MARKET_SYMBOLS.ETH, clientChild.get_public_address(), True) + print("Sub account created: {}\n".format(status)) + + + # must open socket before subscribing + print("Making socket connection to firefly exchange") + await clientChild.socket.open() + + # subscribe to parent's events + resp = await clientChild.socket.subscribe_user_update_by_token(clientParent.get_public_address()) + print("Subscribed to parent's events:",resp) + + + # triggered when status of any user order updates + print("Listening to parents position updates") + await clientChild.socket.listen(SOCKET_EVENTS.POSITION_UPDATE.value, callback) + + clientChild.add_market(MARKET_SYMBOLS.ETH) + + parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) + + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # sub account is only whitelisted for ETH market + maker=clientParent.get_public_address(), # maker of the order is the parent account + price=0, + quantity=0.02, + side=ORDER_SIDE.BUY, + orderType=ORDER_TYPE.MARKET, + leverage=parent_leverage, + ) + + # order is signed using sub account's private key + signed_order = clientChild.create_signed_order(signature_request) + + resp = await clientChild.post_signed_order(signed_order) + + print(resp) + + time.sleep(10) + + status = await clientChild.socket.unsubscribe_user_update_by_token(clientParent.get_public_address()) + print("Unsubscribed from user events: {}".format(status)) + + # close socket connection + print("Closing sockets!") + await clientChild.socket.close() + + await clientChild.close_connections() + await clientParent.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/17.1.get_orders_readonly.py b/examples/17.1.get_orders_readonly.py new file mode 100644 index 0000000..7824502 --- /dev/null +++ b/examples/17.1.get_orders_readonly.py @@ -0,0 +1,69 @@ +''' + This example shows how users can get their orders information. + The get_orders route provides a number of optional params that can be + mixed together to fetch the exact data that user needs. +''' + +import sys,os +sys.path.append(os.getcwd()+"/src/") +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_STATUS, ORDER_TYPE +import asyncio + + +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK],TEST_ACCT_KEY) + await client.init(True) + + response = await client.generate_readonly_token() + readOnlyclient = FireflyClient(True, Networks[TEST_NETWORK]) + await readOnlyclient.init(True,response) + + + + + print("Get all ETH market orders regardless of their type/status") + orders = await readOnlyclient.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + }) + print('Received orders: ', len(orders)) + + print("Get orders based on status (OPEN and PENDING)") + orders = await readOnlyclient.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + "statuses": [ORDER_STATUS.OPEN, ORDER_STATUS.PENDING] + }) + print('Received orders: ', len(orders)) + + + print("Get an order 180318 using id (possible this order is not available anymore)") + orders = await readOnlyclient.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + "orderId": 180318 + }) + print('Received orders: ', len(orders)) + + print("Get orders using hashes (possible these orders are not available anymore)") + orders = await readOnlyclient.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + "orderHashes": [ + "0x21eeb24b0af6832989484e61294db70e8cf8ce0e030c6cfbbb23f3b3d85f9374", + "0xd61fe390f6e6d89a884c73927741ba7d2d024e01f65af61f13363403e805e2c0"] + }) + print('Received orders: ', len(orders)) + + print("Get only MARKET orders for ETH market") + orders = await readOnlyclient.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + "orderType": [ORDER_TYPE.MARKET] + }) + print('Received orders: ', len(orders)) + + await readOnlyclient.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/17.get_orders.py b/examples/17.get_orders.py new file mode 100644 index 0000000..c4a80af --- /dev/null +++ b/examples/17.get_orders.py @@ -0,0 +1,61 @@ +''' + This example shows how users can get their orders information. + The get_orders route provides a number of optional params that can be + mixed together to fetch the exact data that user needs. +''' +import sys,os +sys.path.append(os.getcwd()+"/src/") +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_STATUS, ORDER_TYPE +import asyncio + +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + + print("Get all ETH market orders regardless of their type/status") + orders = await client.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + }) + print('Received orders: ', len(orders)) + + print("Get orders based on status (OPEN and PENDING)") + orders = await client.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + "statuses": [ORDER_STATUS.OPEN, ORDER_STATUS.PENDING] + }) + print('Received orders: ', len(orders)) + + + print("Get an order 180318 using id (possible this order is not available anymore)") + orders = await client.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + "orderId": 180318 + }) + print('Received orders: ', len(orders)) + + print("Get orders using hashes (possible these orders are not available anymore)") + orders = await client.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + "orderHashes": [ + "0x21eeb24b0af6832989484e61294db70e8cf8ce0e030c6cfbbb23f3b3d85f9374", + "0xd61fe390f6e6d89a884c73927741ba7d2d024e01f65af61f13363403e805e2c0"] + }) + print('Received orders: ', len(orders)) + + print("Get only MARKET orders for ETH market") + orders = await client.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + "orderType": [ORDER_TYPE.MARKET] + }) + print('Received orders: ', len(orders)) + + await client.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/18.dms_api.py b/examples/18.dms_api.py new file mode 100644 index 0000000..8db738e --- /dev/null +++ b/examples/18.dms_api.py @@ -0,0 +1,55 @@ +import json + +import sys,os +sys.path.append(os.getcwd()+"/src/") +from config import TEST_ACCT_KEY, TEST_SUB_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, Networks, OrderSignatureRequest +import asyncio + +from bluefin_client_sui.interfaces import PostTimerAttributes + + + +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + print("User: ", client.get_public_address()) + client.add_market(MARKET_SYMBOLS.BTC) + client.add_market(MARKET_SYMBOLS.ETH) + + countDownsObject: PostTimerAttributes = dict() + countDowns = list() + countDowns.append({ + 'symbol': MARKET_SYMBOLS.BTC.value, + 'countDown': 3 * 1000 + } + ) + + countDowns.append({ + 'symbol': MARKET_SYMBOLS.ETH.value, + 'countDown': 3 * 1000 + } + ) + + countDownsObject["countDowns"] = countDowns + try: + # sending post request to reset user's count down timer with MARKET_SYMBOL for auto cancellation of order + postResponse = await client.reset_cancel_on_disconnect_timer(countDownsObject) + print(postResponse) + # get request to get user's count down timer for MARKET_SYMBOL + getResponse = await client.get_cancel_on_disconnect_timer({ + 'symbol': MARKET_SYMBOLS.ETH + }) + print(getResponse) + + except Exception as e: + print(e) + + await client.close_connections() + + +if __name__ == "__main__": + event_loop = asyncio.get_event_loop() + event_loop.run_until_complete(main()) diff --git a/examples/19.Generate_readonly_token.py b/examples/19.Generate_readonly_token.py new file mode 100644 index 0000000..083b73a --- /dev/null +++ b/examples/19.Generate_readonly_token.py @@ -0,0 +1,37 @@ + +import sys,os +sys.path.append(os.getcwd()+"/src/") +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks +from pprint import pprint +import asyncio + + + +async def main(): + # initialize client + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + # initialize the client + # on boards user on firefly. Must be set to true for first time use + # + await client.init(True) + + print('Account Address:', client.get_public_address()) + + # # generates read-only token for user + data = await client.generate_readonly_token() + + print("Read-only Token:",str(data)) + + await client.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/2.user_info.py b/examples/2.user_info.py new file mode 100644 index 0000000..0e91289 --- /dev/null +++ b/examples/2.user_info.py @@ -0,0 +1,42 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS +from pprint import pprint +import asyncio + +async def main(): + # create client instance + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + # initialize the client + # on boards user on firefly. Must be set to true for first time use + await client.init(True) + + # gets user account data on firefly exchange + data = await client.get_user_account_data() + + pprint(data) + + position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) + + # returns {} when user has no position + pprint(position) + + position = await client.get_user_position({"symbol":MARKET_SYMBOLS.BTC}) + + # returns user position if exists + pprint(position) + + await client.close_connections() + + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/3.balance.py b/examples/3.balance.py new file mode 100644 index 0000000..93a51ed --- /dev/null +++ b/examples/3.balance.py @@ -0,0 +1,53 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") + +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks +import asyncio + + + +async def main(): + # create client instance + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + # initialize the client + # on boards user on firefly. Must be set to true for first time use + await client.init(True) + + # checks chain native token balance. + # A user must have native tokens to perform contract calls + + print('Chain token balance:', await client.get_native_chain_token_balance()) + + # check margin bank balance on-chain + print('Margin bank balance:', await client.get_margin_bank_balance()) + + # check usdc balance user has on-chain + print('USDC balance:', await client.get_usdc_balance()) + + # deposit usdc to margin bank + # must have native chain tokens to pay for gas fee + print('USDC deposited:', await client.deposit_margin_to_bank(10)) + + # check margin bank balance + resp = await client.get_margin_bank_balance() + print('Margin bank balance:', resp) + + # withdraw margin bank balance + print('USDC Withdrawn:', await client.withdraw_margin_from_bank(resp)) + + # check margin bank balance + print('Margin bank balance:', await client.get_margin_bank_balance()) + + await client.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/4.placing_orders.py b/examples/4.placing_orders.py new file mode 100644 index 0000000..68ffb75 --- /dev/null +++ b/examples/4.placing_orders.py @@ -0,0 +1,106 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") +from bluefin_client_sui.utilities import toSuiBase +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +import asyncio + + + +async def place_limit_order(client: FireflyClient): + + # default leverage of account is set to 3 on firefly + user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) + + # creates a LIMIT order to be signed + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # market symbol + price=toSuiBase(1636.8), # price at which you want to place order + quantity=toSuiBase(0.01), # quantity + side=ORDER_SIDE.BUY, + orderType=ORDER_TYPE.LIMIT, + leverage=toSuiBase(1) + ) + + # create signed order + signed_order = client.create_signed_order(signature_request) + + print("Placing a limit order") + # place signed order on orderbook + resp = await client.post_signed_order(signed_order) + + # returned order with PENDING state + print(resp) + + return + +async def place_market_order(client: FireflyClient): + + + # default leverage of account is set to 3 on firefly + user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) + + # creates a LIMIT order to be signed + ''' + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # market symbol + price=0, # price at which you want to place order + quantity=0.01, # quantity + side=ORDER_SIDE.BUY, + orderType=ORDER_TYPE.MARKET, + leverage=user_leverage + ) ''' + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, + # market = "0x25a869797387e2eaa09c658c83dc0deaba99bb02c94447339c06fdbe8287347e", + price = toSuiBase(0), + quantity = toSuiBase(1), + leverage = toSuiBase(1), + side = ORDER_SIDE.BUY, + reduceOnly = False, + postOnly = False, + orderbookOnly = True, + maker = "0xa3c3504d90c428274beaa89f1238a769ea1d1c3516c31c0f4157f33787367af0", + expiration = 1700530261000, + salt = 1668690862116, + orderType = ORDER_TYPE.MARKET, + ) + + # create signed order + signed_order = client.create_signed_order(signature_request) + + print("Placing a market order") + # place signed order on orderbook + resp = await client.post_signed_order(signed_order) + + # returned order with PENDING state + print(resp) + + + return + +async def main(): + + # initialize client + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + await client.init(True) + + # add market that you wish to trade on ETH/BTC are supported currently + client.add_market(MARKET_SYMBOLS.ETH) + + # await place_limit_order(client) + await place_market_order(client) + await place_limit_order(client) + + await client.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/5.adjusting_leverage.py b/examples/5.adjusting_leverage.py new file mode 100644 index 0000000..0734be3 --- /dev/null +++ b/examples/5.adjusting_leverage.py @@ -0,0 +1,39 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") + +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS +import asyncio + + +async def main(): + + # initialize client + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + await client.init(True) + + print('Leverage on BTC market:', await client.get_user_leverage(MARKET_SYMBOLS.BTC)) + # we have a position on BTC so this will perform on-chain leverage update + # must have native chain tokens to pay for gas fee + await client.adjust_leverage(MARKET_SYMBOLS.BTC, 6) + + print('Leverage on BTC market:', await client.get_user_leverage(MARKET_SYMBOLS.BTC)) + + + print('Leverage on ETH market:', await client.get_user_leverage(MARKET_SYMBOLS.ETH)) + # since we don't have a position on-chain, it will perform off-chain leverage adjustment + await client.adjust_leverage(MARKET_SYMBOLS.ETH, 7) + + print('Leverage on ETH market:', await client.get_user_leverage(MARKET_SYMBOLS.ETH)) + + await client.close_connections() + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/6.adjusting_margin.py b/examples/6.adjusting_margin.py new file mode 100644 index 0000000..a2f6db8 --- /dev/null +++ b/examples/6.adjusting_margin.py @@ -0,0 +1,48 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") + +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ADJUST_MARGIN +from eth_utils import from_wei +import asyncio + +TEST_NETWORK="SUI_STAGING" +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) + print("Current margin in position:", from_wei(int(position["margin"]), "ether")) + + # adding 100$ from our margin bank into our BTC position on-chain + # must have native chain tokens to pay for gas fee + await client.adjust_margin(MARKET_SYMBOLS.ETH, ADJUST_MARGIN.ADD, 100) + + # get updated position margin. Note it can take a few seconds to show updates + # to on-chain positions on exchange as off-chain infrastructure waits for blockchain + # to emit position update event + position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) + print("Current margin in position:", from_wei(int(position["margin"]), "ether")) + + + # removing 100$ from margin + await client.adjust_margin(MARKET_SYMBOLS.ETH, ADJUST_MARGIN.REMOVE, 100) + + position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) + print("Current margin in position:", from_wei(int(position["margin"]), "ether")) + + + try: + # will throw as user does not have any open position on BTC to adjust margin on + await client.adjust_margin(MARKET_SYMBOLS.BTC, ADJUST_MARGIN.ADD, 100) + except Exception as e: + print("Error:", e) + + await client.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/7.cancelling_orders.py b/examples/7.cancelling_orders.py new file mode 100644 index 0000000..19affe9 --- /dev/null +++ b/examples/7.cancelling_orders.py @@ -0,0 +1,84 @@ +import sys,os, random +sys.path.append(os.getcwd()+"/src/") + +import time +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui.utilities import toSuiBase +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, ORDER_STATUS, OrderSignatureRequest +from pprint import pprint +import asyncio + + + +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + # must add market before cancelling its orders + client.add_market(MARKET_SYMBOLS.ETH) + #client.create_order_to_sign() + #await client.adjust_leverage(MARKET_SYMBOLS.ETH, 1) + + order = { + #"market": "0x25a869797387e2eaa09c658c83dc0deaba99bb02c94447339c06fdbe8287347e", + "symbol":MARKET_SYMBOLS.ETH, + "price": toSuiBase(1000), + "quantity":toSuiBase(1), + "side":ORDER_SIDE.SELL, + "orderType":ORDER_TYPE.LIMIT, + "leverage":toSuiBase(1), + "expiration":int(time.time()+(30*24*60*60))*1000, # a random time in future + "reduceOnly":False, + "salt":111000000, + "postOnly":False, + "orderType": ORDER_TYPE.LIMIT, + "orderbookOnly": True + + } + # creates a LIMIT order to be signed + order = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # market symbol + price=toSuiBase(1636.8), # price at which you want to place order + quantity=toSuiBase(0.01), # quantity + side=ORDER_SIDE.BUY, + orderType=ORDER_TYPE.LIMIT, + leverage=toSuiBase(1), + salt=random.randint(0,100000000), + expiration=int(time.time()+(30*24*60*60))*1000 + ) + + + signed_order = client.create_signed_order(order) + resp = await client.post_signed_order(signed_order) + + + # sign order for cancellation using order hash + # you can pass a list of hashes to be signed for cancellation, good to be used when multiple orders are to be cancelled + cancellation_request = client.create_signed_cancel_orders(MARKET_SYMBOLS.ETH, order_hash=[resp['hash']]) + pprint(cancellation_request) + + # # or sign the order for cancellation using order data + cancellation_request = client.create_signed_cancel_order(order) + pprint(cancellation_request) # same as above cancellation request + + # post order to exchange for cancellation + resp = await client.post_cancel_order(cancellation_request) + + pprint(resp) + + # cancels all open orders, returns false if there is no open order to cancel + resp = await client.cancel_all_orders(MARKET_SYMBOLS.ETH, [ORDER_STATUS.OPEN, ORDER_STATUS.PARTIAL_FILLED]) + + if resp == False: + print('No open order to cancel') + else: + pprint(resp) + + await client.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/8.exchange_data.py b/examples/8.exchange_data.py new file mode 100644 index 0000000..0a769cf --- /dev/null +++ b/examples/8.exchange_data.py @@ -0,0 +1,66 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") + +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, TRADE_TYPE, Interval +from pprint import pprint +import asyncio + +TEST_NETWORK="SUI_STAGING" + +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + + # returns status/health of exchange + status = await client.get_exchange_status() + pprint(status) + + # gets state of order book. Gets first 10 asks and bids + orderbook = await client.get_orderbook({"symbol": MARKET_SYMBOLS.ETH, "limit":10}) + pprint(orderbook) + + # returns available market for trading + market_symbols = await client.get_market_symbols() + print(market_symbols) + + # gets current funding rate on market + funding_rate = await client.get_funding_rate(MARKET_SYMBOLS.ETH) + pprint(funding_rate) + + # gets markets meta data about contracts, blockchain, exchange url + meta = await client.get_market_meta_info() # (optional param MARKET_SYMBOL) + # should log meta for all markets + pprint(meta) + + # gets market's current state + market_data = await client.get_market_data(MARKET_SYMBOLS.ETH) + pprint(market_data) + + + # gets market data about min/max order size, oracle price, fee etc.. + exchange_info = await client.get_exchange_info(MARKET_SYMBOLS.ETH) + pprint(exchange_info) + + # gets market candle info + candle_data = await client.get_market_candle_stick_data({"symbol": MARKET_SYMBOLS.ETH, "interval": Interval._1M}) + pprint(candle_data) + + # gets recent isolated/normal trades on ETH market + recent_trades = await client.get_market_recent_trades({"symbol": MARKET_SYMBOLS.ETH, "traders": TRADE_TYPE.ISOLATED}) + pprint(recent_trades) + + + # gets addresses of on-chain contracts + contract_address = await client.get_contract_addresses() + pprint(contract_address) + + await client.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/9.user_data.py b/examples/9.user_data.py new file mode 100644 index 0000000..5525e53 --- /dev/null +++ b/examples/9.user_data.py @@ -0,0 +1,69 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") + +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_STATUS +from pprint import pprint +import asyncio + +TEST_NETWORK="SUI_STAGING" +async def main(): + + client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + + # returns user account (having pvt key and pub address) + user_account = client.get_account() + print('account:', user_account) + + # returns user public address + pub_address = client.get_public_address() + print('pub_address:', pub_address) + + # used to fetch user orders. Pass in statuses of orders to get + orders = await client.get_orders({ + "symbol": MARKET_SYMBOLS.ETH, + "statuses": [ORDER_STATUS.OPEN, ORDER_STATUS.PENDING] + }) + + print("User open and pending orders:") + pprint(orders) + + # fetches user transaction history. Pass page number and size as the route is paginated + tx_history = await client.get_transaction_history({ + "symbol": MARKET_SYMBOLS.ETH, + }) + print("User transaction history:") + pprint(tx_history) + + # gets user current position + # must add market to client before using this method + client.add_market(MARKET_SYMBOLS.ETH) + position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) + + print("User position:") + pprint(position) + + # fetches user trades + trades = await client.get_user_trades({"symbol":MARKET_SYMBOLS.BTC}) + print("User trades:") + pprint(trades) + + + # fetches user account's general data like leverage, pnl etc. + account_data = await client.get_user_account_data() + print("Account data:") + pprint(account_data) + + + user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.BTC) + print("Account leverage:", user_leverage) + + await client.close_connections() + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/SocketClient.py b/examples/SocketClient.py new file mode 100644 index 0000000..bbeef62 --- /dev/null +++ b/examples/SocketClient.py @@ -0,0 +1,22 @@ +import socketio + +# Create a Socket.IO client instance +sio = socketio.Client() + +# Define an event handler for the 'connect' event +@sio.event +def connect(): + print('Connected to server') + # Subscribe to the 'message' event + # sio.emit('subscribeToRoom', "room1") + +# Define an event handler for the 'message' event +@sio.event +def roomMessage(data): + print('Received message:', data) + +# Connect to the Socket.IO server +sio.connect('http://localhost:3000') + +# Wait for events and keep the connection alive +sio.wait() \ No newline at end of file diff --git a/examples/SocketServer.py b/examples/SocketServer.py new file mode 100644 index 0000000..7843523 --- /dev/null +++ b/examples/SocketServer.py @@ -0,0 +1,42 @@ +import socketio +import eventlet +import time + +# Create a Socket.IO server instance +sio = socketio.Server() + +# Define an event handler for the 'connect' event +@sio.on('connect') +def handle_connect(sid, environ): + print('Client connected:', sid) + background_task() + + +# Define an event handler for the 'message' event +@sio.on('message') +def handle_message(sid, data): + print('Received message:', data) + # Broadcast the received message to all connected clients + sio.emit('message', data) + +# Define the broadcast task +def broadcast_message(): + message = "Hello from the server!" + print(message) + sio.emit('message', message, namespace='/') + +# Background task to broadcast messages at an interval +def background_task(): + print("hello") + while True: + time.sleep(5) # Adjust the interval as needed + broadcast_message() + +# Run the Socket.IO server +if __name__ == '__main__': + app = socketio.WSGIApp(sio) + socketio.Middleware(sio, app) + eventlet.wsgi.server(eventlet.listen(('', 3051)), app) + print("hello after") + + diff --git a/examples/config.py b/examples/config.py new file mode 100644 index 0000000..0973abe --- /dev/null +++ b/examples/config.py @@ -0,0 +1,3 @@ +TEST_ACCT_KEY = "milk fit tape notable input seek circle define deny rally camera sorry" +TEST_SUB_ACCT_KEY = "7540d48032c731b3a17947b63a04763492d84aef854246d355a703adc9b54ce9" +TEST_NETWORK = "SUI_STAGING" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8f32b5c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "bluefin_client_sui" +version = "0.1.0" +description = "Library to interact with firefly exchange protocol including its off-chain api-gateway and on-chain contracts" +readme = "README.md" +requires-python = ">=3.8" +keywords = ["bluefin", "exchange", "decentralized", "perpetuals", "blockchain"] +dependencies = [ + 'web3 ~= 5.31.3', + 'requests ~= 2.28.1', + 'python-socketio ~= 5.7.2', + 'websocket-client ~= 1.5.1' +] + +[project.urls] +"Homepage" = "https://github.com/fireflyprotocol/bluefin-client-python-sui" +"Bug Reports" = "https://github.com/fireflyprotocol/bluefin-client-python-sui/issues" +"Source" = "https://github.com/fireflyprotocol/bluefin-client-python-sui" + +[build-system] +requires = [ + "setuptools>=42", + "wheel" +] +build-backend = "setuptools.build_meta" diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..acbdc0c --- /dev/null +++ b/requirements.in @@ -0,0 +1,49 @@ +aiohttp==3.8.3 +aiosignal==1.3.1 +async-timeout==4.0.2 +attrs==22.1.0 +base58==2.1.1 +bidict==0.22.0 +bitarray==2.6.0 +certifi==2022.12.7 +charset-normalizer==2.1.1 +cytoolz==0.12.0 +eth-abi==2.2.0 +eth-account==0.5.9 +eth-hash==0.5.1 +eth-keyfile==0.5.1 +eth-keys==0.3.4 +eth-rlp==0.2.1 +eth-typing==2.3.0 +eth-utils==1.9.5 +ethereum-utils==0.5.0 +frozenlist==1.3.3 +hexbytes==0.3.0 +idna==3.4 +importlib-resources==5.10.1 +ipfshttpclient==0.8.0a2 +jsonschema==4.17.3 +lru-dict==1.1.8 +multiaddr==0.0.9 +multidict==6.0.3 +netaddr==0.8.0 +parsimonious==0.8.1 +pkgutil_resolve_name==1.3.10 +protobuf==3.19.5 +pycryptodome==3.16.0 +pyrsistent==0.19.2 +pysha3==1.0.2 +python-engineio==4.3.4 +python-socketio==5.7.2 +pywin32==305 +requests==2.28.1 +rlp==2.0.1 +six==1.16.0 +toolz==0.12.0 +urllib3==1.26.13 +varint==1.0.2 +web3==5.31.3 +websockets==9.1 +yarl==1.8.2 +zipp==3.11.0 +websocket-client==1.5.1 \ No newline at end of file diff --git a/res/banner.png b/res/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..e109051a1f23d29fe4a3125678e72d48dce77790 GIT binary patch literal 23051 zcmeIZ2Uio__ck0vK_rTbN+^noidZ11lmLnp>C%g|5J0NZrB_9oQY;`SK|lyd=+dNz z8U+GKl@bUdM0%vRz&n$0|JL(5`iQaA9#)GIWDLPF)25FeqzZpa4H)aMMBq z=r^XPj=JMg1HhO>&wFY#g-Uq=0iFJqC=JfS(DU!~o1f*l23gH(H%I4Z)z%4W3t&Q~86 z^ts`S}GJyXFfxxZy z!@w)54LfIE%kEMupVK%n+k1@B0Sf*Yc>LCP+O3g$v;67$qEW9U% zU6qk`N95g=<=*Lo#vFGhzec5Q3_SWzS@XT;4qySCB%MZb;5zd&wBBl1=v_QcW>XS z^X+x(Bw1$AAyc3F zTh#BVD)30SNn3d>I$&iWsjeh@>pvuz_R#=$mhEkyw;)p}<`EAf1_}%mp-lbptNZA( zP>p*y><4TsEoMNlkhX3`n)V0s_-#~j{3l744ub~vV~r63Gut>D!eK;3FR2$r{SS6o z`eGCo3c)^HZ@$8JHF01?zZ z0)8VUQ0hBw#oQarDh<85w-sjw`hHV>9O>!6%s!{oYs5AUM`3+7?XT}W-L4!2-W*iE z7Ef+SoV!a^vJ>{wP@kIre~jKPvv*JFir?1&U3@U)ChqiBEbwABr z7lC;*PbEi<2CzetH67Ttv|8->Glf*rCqB>Y)Rqj-ituXAmh>wg8x`qK44zT-rR&;u zi-9I@@(rbaK!pD!?>2b_lg~&R)irL2Qy1$~ZB#*PS2`hHeC7K_SG`DePpc(5z$K(v zZ6B?`)d7_A+;jlMEq`#pbe5;8+)Udr%B|%Xx0$I~1v{<>;b@jM(=_QXE8P;4dzG4; z^Tl(!B*2$Cb5*D+9+VukaUKU=aVFkXx2w#~J02kOH$ocO7~i?Mos{NH_Y?epL@ky? zUHu`@nuM;oJn_{X!)v3}5@0`~!xOidSr^s8OG{1gZaVr2-1V${ouM$5jd9Xu7ZT!T zb7CyS^EnLlrTG5UzcCHH>ihv_(_aRwm>)#4>h`5 zm4e)+XdtzH^}w0Ze0+T1X}J>ST3K*>_T1RraE3_@K`TD(Y5c+Br$y6K*|kR;{Q`u7 zRJ%p5|5i7Q3Jy+u-ECoqL=$H2TvAq_a!h~8rcKo?8c6)xKvP+qnQjGZBW|QpSXko4 zUoTO*o3hYIxs~4&uJXtuf+L*Pr}BaHdGAG{uq{l2)NZN8d$3iYt!H)o9WRL_zUofk|dFgPS_G=$?D*UDRHM>p%7Jr!G-PqkFSA;gFDz`$rgXTV1SguXJ6It71 zkRl7N3yBP(N6Sq`uQ6>FVVuLP?d95z(L{JSuP06O{{lRe&Yix1}4~R(X%~g%36P#^@D5Rr08*r%8(j4Ga{6g z!xW#JJX&?kBItcwAp3ai3kYN6-4YR#?*e*2k^!H9G~cEB>w6a-Wcb&UYN6WiLSQKU zUMegtR$}*N5Ubv8|B?@tS|UPlydC%e>hYfcu*4)+*IccFKOG5$?Mt|Bd4^)r@?-Z9 zsUFPcV!X#k*udL^gz0qxSd5mFC8!Q&7Nc2vQRj+D}fP;$=f}JD+dEw zyTu5U2r)TN_qPmnz`qu?PP0AB>IsEmGxMP-&jmbmTg)uc(?6jk6IU^M!o*$N+ap$Y zs3AZ->rjT#l30~702~+YiOQ5#WOsgk)j?7lmcPN6phw4D@IBofbjHQ2I!*4WysYbK z6&Q6Iy0tIiPFR@Gr&ua1_;?}WtB3nFX;y2!_Ck8$Vc#=X7RA!5Zno}b`n5?kZ)xBV zB%=l8>`<#dr6J68@z)TfU2Th2UEs=l);c8cOAF>F_A)wHptn6w|8Rn6SlC)~XMKU# z*P7+CK{vZ=Xj@l1PT%^ak#U>+g^X|^i|8j6NeO(^?aBKmak}0^^_2UOn#An{NPEuU znY`za%iv>hnY~qP!ODY)ElJ?3Mhf{5k!^9>?|7Zq7MS6BgC5T%eyK;^HEj_i?ACJN z-cM*dHtoNB%$HzQlC0_<~Kku(MJYnq9>!LgOIK|*c z25W(KRDc`M7);RnOj_*@iqO67U2YP8BR<%mvfA<5@}%4RYYB_-O7iZSHADwq zmB%$^m4;Dy{=z4FWb-+a5RXyoTBh@Q8ps`Y%ESV4KU@T)03Wim33vQ1stmVi?Kpp` z>&DE+qtC=09~IrjTSGWLc?*?rjrUZ&n~AOA9A2Lb1_eXb5ANAWrKG4bfsHNOx{%lH z3Px&_A@uS~1UamC)tTaP_ZIx#Na)eQR+mKeBAppUC zDE@2lScozFk$4iEVc_spdwHZ6hR%0@FFvzZ#M~)8z3p>71M`{inW(5TV zv45jgW%d++DU{?{@=cnw>~cN+DGG0^JnoY1?U-`_;_Q0yIzA!x^TK}c2^F7US=(lth4Dtr5!F9JfpXGLDbX~r*-0Z*d4pZGaC3W9n7z(3LH*z=gC zvv;;0kKrdqH0q&Cf}Y(CqJNG9;(w1eil;BrVgRDPm%3DY5nkP*Ld(IH0@RsU)68y` z5rLNg8*?B5tC?x8S@PtW97BQXLDcWjldozKKw*&DhKB=+J7;#jqTQm^4Z_D1G1okg zjN~vwC|ZCb;_s}p-vAwkDUqK3bm*v(w86|(5gkQR1VT=GXG5S8S;n!gs%(`@<5NLw zm9AFT9PYXI1kyp&*((vVAldXB51`x>$Xs*m#!+g}i#}jI1;}AnjLYSzdo&f%+VoIt z({4II_2IY0SqI&r#=lr>C@Y>+a{Lmelq{ zF^89UUPhEc(i8Zuq~gVSwQk7HxM)(Wz68QwOC_hV(%Di4Z-@8%uuz?ENDlk;sqAVE zwQ;6$&U~wygOKa1=uS1|!EglUzZ13m82iO*8x}ptHg*i$SC9_f1gOVATGyWeDh=DT z`zYJhWgqo1e>DNC5^wd~!!(vs_-Jh^3B7Za&trKY&Pni#17?sb?}?AgO@-f|1p^&d zRb$k>%RYns;6hkyxsJY$J@az>#N=cX`{Btz^47ZOB`^K$affaEn0+RDt&qE+Luu|| zYd@dF_D|lkA9h_4nZ<(v$5)Bn23rDTLYnr@u%D;fq%qeZt`)Y(iqGYUG~r&Jda^hN zD@+S)E_-`)m2>6JW|AGN9~ca6$CCrVL=QhTcdZNY%aNkR%6stYZ!{l<2h&rx<*MSD z%`YUzY_>Ch4>GRTX9vQtuEN3g+>s-W3T3A| zlYe^1kI|x{x_E0kpaUE<$IRPc@hOXHnk+@bG>a+*$g&>b!LM*U(qji68eu zEm0R-kvy$6qIgeX7KQ4V0GoP7GZ5<{HM*A&k0;C*=N_;;9d~8tRlX@f7=MR=nu%{(G)hJcSnDHg7J31y9=38SwjeG&-p8IZ5U#keJrffi#mgI^!Wxms!H7 znVp&6w-5CUEM}7NUzX%%>Syg@Hbx!TJZEg9xA0>jQ-4Lj6%2(6MGx_TfI(m^^aP}k zkz^$S#^_54VocZxpX7sL6d{1#t|B-f?0>#SeUA0*7A@GlFzCN9K4-pCDw4vhlXKYY zcSA#xb)qAU`an3;>FB@yj(OY|%l42b9=SGtE(Dp+jg~iT6SxfxVsYa^Cj1cn#OVV!Ah1;Mi+tUfVL?Qcw{KW;a}yS2{-?mWoxMNKz7LoeS!QBpfZ`LfSH~ zifs*r6%AhbaV0CK+QISgg?ikWFY(1`IcpIFS)=yk71B0U^r$fRXq zK|`>wPz&YwSY_f!lhm8wPo2D6-Mw?2hHQ`P$Kw0P z%m5}?&z0v=EY$@v+qcq5t3&Ggtu1QN935s1kjOjO@w%$e6EpucmqAPP+(D`@F1p|E z{&;_8*OW(qcT&4henUr`ZXY&xOa&5Y=qnW&>(~snV|?K*ux8QDLgO9~bj;rB8HfSh z1DPIgJVHVa9ERGwuADM2DUyu88NwJ(cG#+K6F(w!@bb>*!@{~6X1R1zW?sU9P~t*t znZ-m6sHC1#K14HW12aSQtP8CI#9V;090nhR zs5kM`jDa==I(F`1FHr&p7Aym%E!Cu8*Os-RwY#*c zi;LK+rH%yUj4R@A=UE0hMhqW_Z&Yj#|2^BCL~MeP~Q zXE~G7k8J&k*#XX%0K6mm=iD`F1b6eUd5lz*^Fq+yhMX;=*Ne5^NisY2(M`_22Xout zzZ>|3x8EJ2CL!dMC%*|yO8@wkR zhx369%^1XH&-_b6SG+Kmdc3gTL^QA}cKK8F?Mq7Ym`eI4g{En(ELuiV(#_a%$g#TanDgDD0kpG47`pLX&Pq}wm98i2)_YO6UW{>sC- z;P`8Ow-=*$t5Sv7FD5`IAPR6UI=djH(aExS2|!zuKM%am)NMy--!WCT&m4+a*cHGz zpSc)UV*hBJTxXz~$bgFxyor8rUiz|Dpyb!TLxi^@WQVU~KZaDdb{<~DJduq1!WDlu zvkEykduLtu4~_JrM)pi)pA^1zQ9R#qVL6k0>Qu#Fs`M@=T0^qItE9-6a8~Eewd?vC zp=B1K&>9n;qiS)J;*}f%)wb_eTeY_5(TkB`(c}pF0y2tpu{xdh@KP$hAeTb`fnCr?{4&Rs< zZ?R~{2PfNn4@P~7`(mc(H+0RRIX|ETF(0G{jtE{9ZkYQnK*7Q*_;cuNL$AH>*VlY~ zmXWUNW!XtY-+iv2OF?)&DgtO^d(sH7Q~UVogW|1#52N)n-8Q!<=5uc4{_4!QiNS2u zjf|b%{5)XZP}65}Cd!wx^|uC&Nff>_C(5R947r{Kb`KJ9P8K-T zjm>xxz3hOc0AcoNx@#z`mR=?Q>e8)!bHCI{ZzjL3ra8Dt8L zb&b_#CL5gB2RKRJC3A={T>685hncEMHaL-T`9320Ba*y65-O_A73o`V&{x)>jpW3iqL&&-^|xYd z()~)i-2i6)RUD`(KzH=}rXFMQ^zqSmR}J(podY@=M>E*l@#YBC!THjNbN3!MC!{-` z+N8-^htMq@@B?ISZoO=RYGt0Qu00_Kny?(%*E~SpiVH6p3#p637=z7%cy#pxUu|W8xt6L2Gocr6~8V7MStIm5^kwFZ;OYSI-YDw1)jJ zuPQ~XKjKD)D}Qme64B;rJ-c;$CF$!!hb_6Ju4IdAEf(8UXWFC`vd&3Y@s2?J7va3R zsHODoxkf{G37c9W-u|i3Q%oq`Rh342wFLF(L}4cU94pX{SsdRM^Yttv_QXxH?!H%L zO4PkGdqf$F|Cwf4>!jzYcI26cNBnmsOH-L|CsBE4e;gwg`Ll55^8?Mwi z%UtLCg!h4ajW225R8qXM;e+q}Cf(PEE(Be-Q{JRD!{0CjDjO}vE^m`}NuB@)x+j3&(GTN zFhuLrR@V6N4Q8Ct^Ey{LbZW$QIQ;dumlrP|rv<|HdjkY0$?MaIL1iN0yQ}jo7R&Yg zX!NI>p2aa_yG09fou|P*QbO!%h;8bXsEpHR75&aQED=42gs<=&dO;JSc4*H{KFyvo z(K8SCRhm?9Ytl-R@?P$!dpi*;G$hO|erfKp*?QduENag7y`!f?En*6PIo(f`CSd*M ze*#*(uNDhI?`KMEcDwRVs_$>;jsS-7in`?OFx%9TT0b{YZKFu@)+t9%{}SHoy5z4X z`84l|U{el)a&Ynm_qo9@(ym{ZwwqA4>g~yqY12<6>L|a%qekK@xruE0WsIbIx7Ebb zqG~j_Ut6qeUkfZO8hUxErTi9OBuQ3jUn(d760=v%6q)8&?M~=K70rkbva5gW!NDK* z4yS~eUxXl)1{VS-?HP_Za=;Lz4B|&B!>$len3aakfK0>f0r&?2507ICn>REtCamjiD z{#;N!Jw0JDPpLVUq;hq*31HsXq#{NVVgg+o{m#Cotf=mwo}d$`d5|2v+k!6F3=$u! zzY9+NASuj&pZNWKe9J`YJ~GmYtr!mJ5Xz+5rXm;GEz&O}(0B*#!9h6qJX}-H)dy;s@CFTI69ww#)4iQS>bto=KIgDDS)8HTmy3u~M?iL!?v$ocCCZVM{@ffT& z{E&B{n={1@A{A4Cq2C07@zK5narO@lnC8v=gev?JH)fwClY@VJJC<0TUsL!90pGBH z+Voo0@FlJH#nJooAWnmcs*{5p3K`vqn!e}ine373nOI;8Z#&@BZ)V;xstYpl-Smt+483QQ$Iez82!onM|NP@$UipKHa+geSwUf^DyNTkOD2CzVF?1F} zK98eqz5%fJ_Y>ov(c&&uV<8p+I}iP~sKKaP5^gRS#K4gZlabMN;xq;#&V-QOjsYKm zpi;p1U?J9BDgNg!&av{{66=o`@0;S;CI6l}Qv%-)k;+D|(pRA)7Q`uHYlcdnRObmi zH;slgd^=rALmm*)K|c{eG1-~HV&3wrH@YMyC$lN2vNA$CvUoc^1cYeBWpB!igLc)x z&$zkaN8fe+z`;)$!Ga><76af$B@jmTvzZ?IVfzI@)iOTPiV-gIM-BWAcsow>$>ejG z!hKdoc%kcY6s!d6@qX!-1YZTU_@a2{yR$ThkmJLF55p~sg(C40>BPr6d^O}Rr}Z~d<1nTEm{bywiO&-z z0^m0Y3oqt^STr%q$YR+Q5BejMAfpL-FKlb3Bmm2e#d>W_m^BqtEq7(DcPH;?NcpY9 zVYv;bLgyd}C(?!p)zF1C%Vv@Iy4;}e1;dp^Wj{@gIgM3Y&ptlQMVqQ;B<<~Z-Tdrj zK>xbz{2HDlb^cHc7W=@+C~|kDcIP%MSMfTOmJv|J1F|451d*z1jrw|~U~ zi*DKaqKc0g!yuOwRni8`Zx1P-j=tBK}(bzb*grkP)Ca&i)>Fj6$x8m~xN&cEUe%vcl@0V5kO5@Ao(RTd67KS2bZ=Ie_1n`0-1 z1fNYfP0toO>Ra`HVkx+(?tJ{C{A#W&t_2;Z{F+7A?ah9tBQ^Y?8h{urZ7$^?bLUYn z8zh9~j(luU;*_nFy=g+SG^c}BJ?q8BlqmCC zK-jVaC+Zg%LZ33Ti86{~10pp=IQkV&-nn8vgZ&Z;xM0oArkQ-9l27hq zb}HO*cA=fka1+4Im4D3T&1HtqfxF^ma=yi9f8V>!m0Y=yhkq6KO5V-vNs%9ohoh|N zSxv35{K|8V&_BPozkAK(b{7^D9Ca}Ga1Ma{3Fu`gFA4rGJOiBJi~>+!kIp}{gtzp? z$|1?~o? zH)h3b?=aZb&cR-{o0vqr>hOTRt3Hy!?(9;gEtZEXd$eNGMJ8C@C33{B)eH|*yY`%wV}ZtX=;-w( zQN+RBpseYHOf3s*zu<^}Yk%CB<737)6cdrUt%lKhHm;*LWhv>y_%PyUVB-OU+QmtTz$38EO~Up-kN>uZ6vyt^xAyC-?V zs$ipMVw0N7_N5RcRtR1XqApVsII?2{Mk|Z@=kjuA@dMhlGzXGkaPF)NCNS#pO=CBw(rx2~Cr^Lu;!#DxPlbX8jY3 zyDn+1u5pVNOA8Aky8)uww?^G)iT(29TrlavL$I-Tjzr(%qy)|ox^*~_Ug`U9jXVx$ zHbDAc6h8wxWhZrEVdQ=qPjmamV={@<4J&fp9o`W2Xb8`;%@ziNxVik|S zm}+Zkg1rTaQ1o(`?MvQzC^umWkJ-reuH5-K6t}n*w(EEsB;mN2Jr90A(mt;MFedRg zH+lj$muLP0c}D?jJ#JTQVCIbopw5UfOG_%o0dK63WFf`F4dL0svQ}%Wpc(K9(0W#8 z<{^P!)i&!3!Dr>{?AL!K?~*g0LFeM!c4A;pd8Xn9{QfbE-2G-Ao+>siLs?wG1_w)az>r?U zuI(GHOP>ajEM^C96lq`neszYYegB!CwW<&qJrLK>{~mpH;tiF0-m+z34YH zdx639TW9Qrd`f3C<(F%t!a^%C3SwI>f$6YlZ1I0+C;Eq2Blf2BgV63qV1UVlUnZiG zM^Uq$;~CW9Rz>QL>(FVNm*2#rX-P?3eJ`le0eQ`#72sGS)s) zc(`pJRIaSD)GTvZIr6)uc@Wac#&hZOTH?gGFTfTBlk-2kM85D63VleaHt0>NTg!8| z@#{+1Cr+4qbo8OEqtnDDZ_~nd!?+$m4}cH*BSG+zaHu-pLoD&_1nGTTUr4QR!mgSM ztDN1#^^V=`(Z6$LY|qG)xw?tTj$DXsAz;5Kz(a)Ik@O;!HwN`KHkq0G6Bn$u%ukn{ zpQwE8R=aVpXCW_l`=OFQ93J3Y0CQg|6iB`=P#w>9MD#pnaYJFNx}&K%H;EWvYTX7@ z(YinBKSbf%aa|V{r$~DJ!NInUj9OgBU8jeKAMA#y3PUj=<^en79n$yO-6^fuEB-*G_ncD>E`vBM!hhL|G(#H3 znVtq={e-fhnY&hSsZYB<1C0d(rQv^gcpYrV?4ENdns9!4{9%8av^*06N)-yF~1*@F*gE<$h6(6pCG(<*%F}<#p-RJ_+~6UX+Pr6Le~4zOCd$;c2=+dNIzp8@FUldC3&K=JYWdUh<`A`K3pGP8(DO_}pGHm1s*gq9yB*xH&BiBqubqoF!ArPI_gnV7 zvZ6{J0aNJ5D;Rs=oi1ABOI`FpBmE~PWL^OQ+0lB8yIP3(z5pb*4=9cO0)Y=NZ*BlA zvIv+?Mo#7Bug=8Yp=I8Lvq;*YG2sa?)6{Tx-Fdh@mp|HB0hhm6MVU;@95)c!y|T`k zNArBWq{kHNHG!#G#O4 zFF4*bxrj6}>S?B&(8CI@&d*+F!tIBjpqgwV?Bn%VrP?;wgo8hfyFW6D?vL#%p;-Xy zJ#h-O#52$m9{}%F1o;ohgOGg(;jTi}Pp*u3jjz-R1YQ?v4rWedfS+s8eDZtSn^M$5 z@*1E@!)h05Dt`=HuURtPmkUqL8=uYz)dWRbN6;zB{%qF~Xh8b&Q$W=(J{2Tx7mr!lrr6iDK>cjcKP*Xk;x5&-`M&5kPFqe=;2I@*1U!Tp_xy@vW`}0BdTo38 zqdz{2xB3U}p9`(^I`L4vwx&W`Fdp4SfMuZ9m~-t2@pk~wwOORy`}M7BPJ)>o)wj3q zhqtU8z$tZtFyKbLtgMfSE4_2cTY*+6WVrcufM69xiSv^rPf8+TCOet{NkGNSO(U5a zw}eA>s&>49Zmmw<-Z4|`!_42bgdd&1yJK6e#yuDOdUBe&&PM9udyk8XYs>Ye;bakx zEo3Qo`-uV(2T2fGQYW3QX-Vjrv+LwwgMu!>gzbG7bUFCuVf zt@bF=%VZ#`3`kj~_e`8dOrtrq^rprqA6|uAGU0qKxHkUUGF!z(IO8s7z2iup=uFHU z>Q~&Q&`DtSW%y{>BD9aIV;YVsp#GtbBvm(wi;EBCG;r{-J?n1O*v7@KB?gW<=?8iy zh7bbW5Nou`7eIgE6Ssc~0Gk{hm2fP0tS(kC?)1lT1dDJa-rURl=0_b)^drYnq8(QN z)ylhYZDL+wjj{4La&WMxgf8Gq|%zFOw% zrvfJXfxd=6k*#W6pQVGpBlW~O%?Ln0uH4fM?Nq#lqj8?<}cs2EBy|LAUYVXREtgQX}PhF0J623=S z6AFjXj_I*?)DX#00%o3N`b3lYw4cxNu{>L#w^{DZV$K2a%!f%(K`itqeeI#*Z;Dwe zsrR^)=l!Zi|9b3<-b`XIuUzz0mE+uZwUTem!=o%ad`1`({5Lz(R5xb0Zle2HnY&x;BD9$LZC~WJ$xjHm4 zzqK$DwQ3Zt^}v}IJcxq}>TJFMU#q|^mjiE@aOjBR{J*j;Vk~(z{hw7=Z@qMn^O1$( zC&MW9qk=1z--fddgl*Nbf*k5VS&-Y2<0klJ@NeP2N1ZQXwY|?W(u9MNe@piuaX1~RAmGCk1 zxcOThk=L0Og}+eh$3Iw_7qx73D-$=??V4S#LDXKDah-5#cqLwZf1!2quN=RbZ98&% z#$6{d_$@%YDy5g*k?Niu$-XwUAsy^xIj|5UWd$_s{a)k=YDJ#(bj|KO5mp!%hoJ(X z5X1DQS%j+!3ZHYl0J$m9^Q@Yu*dQ|G`7vyX>$~5z-HdkdZAqVzb-Gp$tqE>-9qp3;=3Se&s7Q3P5#*gum(rJo3O%gVi4v=#dj*f4-U;Qc)}*)ju9m_0yX|jI(_WY}|F}rb*H4iD#_t~8VOj64 z{ozB#NeIa+u2XhlwYoL9v%ap5dfe#pVfDYyTwCH*edUVfP)b-Lb&%h0HexhBwdO&wmfm9nW)!Hgck0G|UZ)kv+)gkqoR?dZRyqK75`UN&oh~{*^IeWo8eE9FrrJ!>M71G~_vMrqap2#ZvbQ&$4 z&2g%)VxiVo>{4S(OPA@n(Bu|G;NtEsu`4R!s1@_rmuyGCBQ9wIe#Zz&Fx}eH8a&99 z8uDZRd;BR2*mDkx&Z+_)zBfw^()7K=(Y&p%!hLIm8exwD{9wKjGcWzoj_OF&SjzWZ z6U$cT8W1dSc;{BLIaC&3Y9SkCd3j#Ehm}>8SaMQ^3zDFP@i!08Wz~4SI`b_#Rv{bq zwCje;PjH#Hs~!2ZMYLZ_iHxKD7DUjyX=071`I~xHghi#gox__3)~3S#)^h+l2Ew-J5+l^^S1;;Hn%f!s&|o(k>WKwP3`k5=B2{GDbe+S@*-bMv07ue zTn3^`URoXGIti!ft;h9VDHpeMm;NhsH{fSbAt(?3HDXlP<5QNvfmR~^oNw^i27^&_l%@7FRxnTufLY83_W}J#nVp)CIhx=o z02iwQu6p+>5oo7h9$Qe^+_&j&8rTEQm;w4S7qeETtzuvis;1*LmLErao{bLR72q9$ zj}*}Hiz|c*?t_f?=NOk_!~f>X=~Ng?w*`hvlveWQI7)Yfz3Y?($mNf19&SP<2Yyg6 zzMHs29x()YDx{hI?o3Juz*nt0GdZs7leZ0lA*{%pT;I);7PfiaqgB;U5E&+qnDzxK z*ojPilytiecwo;A(J*I{yw)tSyn8`_=zv7clPh*Cle*LJ3{-I_(m+5c1M4G6mO(v1 z7=0>saql;9qWcOz4qUfW77Vmw0;Z9oE&q8-Ip?NWzS6NUMh9H7$ApTA2B z`M~d3wrl5K!VST-eR$qE@%#^1%4sc~Q7UavHOP-y`XNhqhj;a?O(4B)L6}7eh&&n# z`BYgmm=C=31W*q})(IsWU(eOSlJOf5xDhimCMt`UNBOjH$&44ER*-T!JuIz!b{7c) z)8_8{gXzVB@+NcOzO8@O?)=?}!sZfSLYMnDX3BZEuU=K%5>5laOAM>z7E*lKGpbm& zRW}Wc_HSl;ZjFRkQ#ZCqA-_L6KF--~z<@hX5uqQy7ZcX(_y#d%(xXV4L-;m=8~n?+KueEBHn%07SVIyh2UO#uaji#j{afp zu&mNv zl|5_$zbN9fA~x}sZPN>BLsXp0K}b=aVe(_1JPj`<1eTk7X4jc^1Y>)e{ySL?+IWRX z!|0!RI!5eBuZk`s&EE1vS2c^9A(VQd%aSX7%*>B4PcJm*a_#!>gTMo<9FUpY%9LxH zi0Qs!dr&=!C3qfo!mt)CS+WjkopM9w7pK}n;x9~1+GAgLhI!yZ7SiZHxcKU z#zHtcf$@Y^C`r4~BT@!vX@~>HP)HCg=YHvVaz`*fR%cVm3zU;Wqz^xgJ^@Vfm_U@D zW_kNYk&n4~?$%DFXE3?+%a@+yprZ{nl=_!f<(xrEa(XBlKYAuI|NQ}oZ4%gi9}?cy zwQINUa~Pr-Bb(m(6yIx4JEJ`xpEOZPZa0|+#=gu#ITPnXsp=Q2oOxQJ@dV+bE}%Lb z#9+A$Zn8o;wJ$cpOP#G)Pls~Qhnl(kI6y;4=W)GAMTm>SxukEMqCzu*y>q# z9h57Zg#{d-D|K~7c7hSdlwXSghjP*`usrnKQMvZ5V9io`AKj<~>V^9Wj45bNQp(HZ zm*)YNNodo^2(@Wgxj(VhJ4cQTv;iFi`~d=MLpM)ZBEiKM157C8^aFW`^UV7cAvg_@ zM{lC6@4MbsD7a+ze#hJ^l^0Y{4Z7zkua*X2UwHnyCv|)95-`A7Na+>PeGn3hO16>V+aAQ77|z#j72ZED_tQ>OF9E+5 z0!Y8hsw+2=TMJmFs-VCeXY(XrYqd+CY>u=O+|oMo#(^AGz6G`!%zRqWS%F4obMLI* z!ak{=Bm;CspyfllucLdB;ad~?kvV+vl_*z4;1`$qq!^$*+M=P-NQ)rcPcVx=QhztT zzS4SBXCO`lmKZoxS^HXIO2y5CFEnjGN8XNym7OFk5u`c2=tk%0iO(FT-a)4sAhqc^ z%IER=alb6oUUyZz(%F%}%nc}x`a*YvNW%rQ8ieufocTuKR;$nc=5_7q*S~=Lq!#-V zdp^nO<9p-iw1F73bcg%!2@~5GuvKsCZ~_j|dGI+qcPfZ%&tF4=Y;>MM2`}sVyD758 ze8OHP>@IrrWhCg+c3_!oA_4ni#>E8ac3Xow07Cq%Z*ZtGaCPg7sDwaL2jZREiz4YQ z2E?Y`i%K@P9f|3mr;KdNM%3L*6l43MCd4lRNn8ZKG6B3?afDFuFwI+Qy(Rqs=YCq| z%6LmN2QGnF;lE%F44-&{0aGt|qylN0?UK5k?fOpP)3x%hFV5>g(4;F{*2kL0NFM87 zayiU8`DAUo+_Mj`^?}$D<)t8Vu$wkga|SJds@Q{R065#2S5$fFB5&onD(-HdU0G_; zdDg(TF@<7C2(0lB!omHBCh1YYTh)<(c{%3%0Eyo=Y2gykMPtU;t zg3B$c#IHA}RZ^>gfCE~E7?H3!M8ufJZYZ(f|3z>U`9drY7=c z3XrQJ5{rGWVvaQ^_K2i|jj(0x32vTLjB@ z0aeS|W||z1P^evF1&JIBWr%js}cf=eJD zxJ5u8S^AU3cfnC}p6ljv{Ek`Dk`34uFvCv*>KKOsWj#A@+#Z}e(H{z8xr771bsh&P zlCIrRiw6Pq(rav{&UtFR!u7 zMtMtt@+x8nI8dA5DD{(LG*!Low)m=B1-H=k;aM 0: + req = self.create_signed_cancel_orders(symbol, hashes, parentAddress) + return await self.post_cancel_order(req) + + return False + + async def post_signed_order(self, params:PlaceOrderRequest): + """ + Creates an order from provided params and signs it using the private + key of the account + + Inputs: + params (OrderSignatureRequest): parameters to create order with + + Returns: + OrderSignatureResponse: order raw info and generated signature + """ + + return await self.apis.post( + SERVICE_URLS["ORDERS"]["ORDERS"], + { + "orderbookOnly": params["orderbookOnly"], + "symbol": params["symbol"], + "price": params["price"], + "quantity": params["quantity"], + "leverage": params["leverage"], + "userAddress": params["maker"], + "orderType": params["orderType"].value, + "side": params["side"].value, + "reduceOnly": params["reduceOnly"], + "salt": params["salt"], + "expiration": params["expiration"], + "orderSignature": params["orderSignature"], + "timeInForce": default_enum_value(params, "timeInForce", TIME_IN_FORCE.GOOD_TILL_TIME), + "postOnly": default_value(params, "postOnly", False), + "cancelOnRevert": default_value(params, "cancelOnRevert", False), + "clientId": "firefly-client: {}".format(default_value(params, "clientId", "firefly-client")) + }, + auth_required=True + ) + + ## Contract calls + async def deposit_margin_to_bank(self, amount): + """ + Deposits given amount of USDC from user's account to margin bank + + Inputs: + amount (number): quantity of usdc to be deposited to bank in base decimals (1,2 etc) + + Returns: + Boolean: true if amount is successfully deposited, false otherwise + """ + + usdc_contract = self.contracts.get_contract(name="USDC") + mb_contract = self.contracts.get_contract(name="MarginBank") + + + # approve funds on usdc + + construct_txn = usdc_contract.functions.approve( + mb_contract.address, + amount).buildTransaction({ + 'from': self.account.address, + 'nonce': self.w3.eth.getTransactionCount(self.account.address), + }) + + self._execute_tx(construct_txn) + + # deposit to margin bank + construct_txn = mb_contract.functions.depositToBank( + self.account.address, + amount).buildTransaction({ + 'from': self.account.address, + 'nonce': self.w3.eth.getTransactionCount(self.account.address), + }) + + self._execute_tx(construct_txn) + + return True + + async def withdraw_margin_from_bank(self, amount): + """ + Withdraws given amount of usdc from margin bank if possible + + Inputs: + amount (number): quantity of usdc to be withdrawn from bank in base decimals (1,2 etc) + + Returns: + Boolean: true if amount is successfully withdrawn, false otherwise + """ + + mb_contract = self.contracts.get_contract(name="MarginBank") + + # withdraw from margin bank + construct_txn = mb_contract.functions.withdrawFromBank( + self.account.address, + amount).buildTransaction({ + 'from': self.account.address, + 'nonce': self.w3.eth.getTransactionCount(self.account.address), + }) + + self._execute_tx(construct_txn) + + return True + + async def adjust_leverage(self, symbol, leverage, parentAddress:str=""): + """ + Adjusts user leverage to the provided one for their current position on-chain and off-chain. + If a user has no position for the provided symbol, leverage only recorded off-chain + + Inputs: + symbol (MARKET_SYMBOLS): market for which to adjust user leverage + leverage (number): new leverage to be set. Must be in base decimals (1,2 etc.) + parentAddress (str): optional, if provided, the leverage of parent is + being adjusted (for sub accounts only) + Returns: + Boolean: true if the leverage is successfully adjusted + """ + + user_position = await self.get_user_position({"symbol":symbol, "parentAddress": parentAddress}) + + account_address = self.account.address if parentAddress=="" else parentAddress + # implies user has an open position on-chain, perform on-chain leverage update + if(user_position != {}): + perp_contract = self.contracts.get_contract(name="Perpetual", market=symbol.value) + construct_txn = perp_contract.functions.adjustLeverage( + account_address, + toDapiBase(leverage)).buildTransaction({ + 'from': self.account.address, + 'nonce': self.w3.eth.getTransactionCount(self.account.address), + }) + self._execute_tx(construct_txn) + + else: + await self.apis.post( + SERVICE_URLS["USER"]["ADJUST_LEVERAGE"], + { + "symbol": symbol.value, + "address": account_address, + "leverage": toDapiBase(leverage), + "marginType": MARGIN_TYPE.ISOLATED.value, + }, + auth_required=True + ) + + return True + + async def adjust_margin(self, symbol, operation, amount, parentAddress:str=""): + """ + Adjusts user's on-chain position by adding or removing the specified amount of margin. + Performs on-chain contract call, the user must have gas tokens + Inputs: + symbol (MARKET_SYMBOLS): market for which to adjust user leverage + operation (ADJUST_MARGIN): ADD/REMOVE adding or removing margin to position + amount (number): amount of margin to be adjusted + parentAddress (str): optional, if provided, the margin of parent is + being adjusted (for sub accounts only) + Returns: + Boolean: true if the margin is adjusted + """ + + user_position = await self.get_user_position({"symbol":symbol, "parentAddress": parentAddress}) + + account_address = Web3.toChecksumAddress(self.account.address if parentAddress == "" else parentAddress) + + if(user_position == {}): + raise(Exception("User has no open position on market: {}".format(symbol))) + else: + perp_contract = self.contracts.get_contract(name="Perpetual", market=symbol.value) + on_chain_call = perp_contract.functions.addMargin if operation == ADJUST_MARGIN.ADD else perp_contract.functions.removeMargin + + construct_txn = on_chain_call( + account_address, + to_wei(amount, "ether")).buildTransaction({ + 'from': self.account.address, + 'nonce': self.w3.eth.getTransactionCount(self.account.address), + }) + + self._execute_tx(construct_txn) + + return True + + async def update_sub_account(self, symbol, sub_account_address, status): + """ + Used to whitelist and account as a sub account or revoke sub account status from an account. + Inputs: + symbol (MARKET_SYMBOLS): market on which sub account status is to be updated + sub_account_address (str): address of the sub account + status (bool): new status of the sub account + + Returns: + Boolean: true if the sub account status is update + """ + perp_contract = self.contracts.get_contract(name="Perpetual", market=symbol.value) + + construct_txn = perp_contract.functions.setSubAccount( + sub_account_address, + status).buildTransaction({ + 'from': self.account.address, + 'nonce': self.w3.eth.getTransactionCount(self.account.address), + }) + + self._execute_tx(construct_txn) + + return True + + async def get_native_chain_token_balance(self): + """ + Returns user's native chain token (ETH/BOBA) balance + """ + try: + return from_wei(self.w3.eth.get_balance(self.w3.toChecksumAddress(self.account.address)), "ether") + except Exception as e: + raise(Exception("Failed to get balance, Exception: {}".format(e))) + + async def get_usdc_balance(self): + """ + Returns user's USDC token balance on Firefly. + """ + try: + contract = self.contracts.get_contract(name="USDC") + raw_bal = contract.functions.balanceOf(self.account.address).call() + return from_wei(int(raw_bal), "mwei") + except Exception as e: + raise(Exception("Failed to get balance, Exception: {}".format(e))) + + async def get_margin_bank_balance(self): + """ + Returns user's Margin Bank balance. + """ + try: + contract = self.contracts.get_contract(name="MarginBank") + return from_wei(contract.functions.getAccountBankBalance(self.account.address).call(),"ether") + except Exception as e: + raise(Exception("Failed to get balance, Exception: {}".format(e))) + + ## Market endpoints + + async def get_orderbook(self, params:GetOrderbookRequest): + """ + Returns a dictionary containing the orderbook snapshot. + Inputs: + params(GetOrderbookRequest): the order symbol and limit(orderbook depth) + Returns: + dict: Orderbook snapshot + """ + params = extract_enums(params, ["symbol"]) + + return await self.apis.get( + SERVICE_URLS["MARKET"]["ORDER_BOOK"], + params + ) + + async def get_exchange_status(self): + """ + Returns a dictionary containing the exchange status. + Returns: + dict: exchange status + """ + return await self.apis.get(SERVICE_URLS["MARKET"]["STATUS"], {}) + + async def get_market_symbols(self): + """ + Returns a list of active market symbols. + Returns: + list: active market symbols + """ + return await self.apis.get( + SERVICE_URLS["MARKET"]["SYMBOLS"], + {} + ) + + async def get_funding_rate(self,symbol:MARKET_SYMBOLS): + """ + Returns a dictionary containing the current funding rate on market. + Inputs: + symbol(MARKET_SYMBOLS): symbol of market + Returns: + dict: Funding rate into + """ + return await self.apis.get( + SERVICE_URLS["MARKET"]["FUNDING_RATE"], + {"symbol": symbol.value} + ) + + async def get_transfer_history(self,params:GetTransferHistoryRequest): + """ + Returns a list of the user's transfer history records, a boolean indicating if there is/are more page(s), + and the next page number + Inputs: + params(GetTransferHistoryRequest): params required to fetch transfer history + Returns: + GetUserTransferHistoryResponse: + isMoreDataAvailable: boolean indicating if there is/are more page(s) + nextCursor: the next page number + data: a list of the user's transfer history record + """ + + return await self.apis.get( + SERVICE_URLS["USER"]["TRANSFER_HISTORY"], + params, + auth_required=True + ) + + async def get_funding_history(self,params:GetFundingHistoryRequest): + """ + Returns a list of the user's funding payments, a boolean indicating if there is/are more page(s), + and the next page number + Inputs: + params(GetFundingHistoryRequest): params required to fetch funding history + Returns: + GetFundingHistoryResponse: + isMoreDataAvailable: boolean indicating if there is/are more page(s) + nextCursor: the next page number + data: a list of the user's funding payments + """ + + params = extract_enums(params,["symbol"]) + + return await self.apis.get( + SERVICE_URLS["USER"]["FUNDING_HISTORY"], + params, + auth_required=True + ) + + async def get_market_meta_info(self,symbol:MARKET_SYMBOLS=None): + """ + Returns a dictionary containing market meta info. + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: meta info + """ + query = {"symbol": symbol.value } if symbol else {} + + return await self.apis.get( + SERVICE_URLS["MARKET"]["META"], + query + ) + + async def get_market_data(self,symbol:MARKET_SYMBOLS=None): + """ + Returns a dictionary containing market's current data about best ask/bid, 24 hour volume, market price etc.. + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: meta info + """ + query = {"symbol": symbol.value } if symbol else {} + + return await self.apis.get( + SERVICE_URLS["MARKET"]["MARKET_DATA"], + query + ) + + async def get_exchange_info(self,symbol:MARKET_SYMBOLS=None): + """ + Returns a dictionary containing exchange info for market(s). The min/max trade size, max allowed oi open + min/max trade price, step size, tick size etc... + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: exchange info + """ + query = {"symbol": symbol.value } if symbol else {} + return await self.apis.get( + SERVICE_URLS["MARKET"]["EXCHANGE_INFO"], + query + ) + + async def get_master_info(self,symbol:MARKET_SYMBOLS=None): + """ + Returns a dictionary containing master info for market(s). + It contains all market data, exchange info and meta data of market(s) + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: master info + """ + query = {"symbol": symbol.value } if symbol else {} + return await self.apis.get( + SERVICE_URLS["MARKET"]["MASTER_INFO"], + query + ) + + async def get_ticker_data(self,symbol:MARKET_SYMBOLS=None): + """ + Returns a dictionary containing ticker data for market(s). + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: ticker info + """ + query = {"symbol": symbol.value } if symbol else {} + return await self.apis.get( + SERVICE_URLS["MARKET"]["TICKER"], + query + ) + + async def get_market_candle_stick_data(self,params:GetCandleStickRequest): + """ + Returns a list containing the candle stick data. + Inputs: + params(GetCandleStickRequest): params required to fetch candle stick data + Returns: + list: the candle stick data + """ + params = extract_enums(params, ["symbol","interval"]) + + return await self.apis.get( + SERVICE_URLS["MARKET"]["CANDLE_STICK_DATA"], + params + ) + + async def get_market_recent_trades(self,params:GetMarketRecentTradesRequest): + """ + Returns a list containing the recent trades data. + Inputs: + params(GetCandleStickRequest): params required to fetch candle stick data + Returns: + ist: the recent trades + """ + params = extract_enums(params, ["symbol", "traders"]) + + return await self.apis.get( + SERVICE_URLS["MARKET"]["RECENT_TRADE"], + params + ) + + async def get_contract_addresses(self, symbol:MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): + """ + Returns all contract addresses for the provided market. + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: all the contract addresses + """ + query = {"symbol": symbol.value } if symbol else {} + + return await self.apis.get( + SERVICE_URLS["MARKET"]["CONTRACT_ADDRESSES"], + query + ) + + ## User endpoints + + def get_account(self): + """ + Returns the user account object + """ + return self.account + + def get_public_address(self): + """ + Returns the user account public address + """ + return self.account.address + + async def generate_readonly_token(self): + """ + Returns a read-only token generated for authenticated user. + """ + return await self.apis.post( + SERVICE_URLS["USER"]["GENERATE_READONLY_TOKEN"], + {}, + True + ) + async def get_orders(self,params:GetOrderRequest): + """ + Returns a list of orders. + Inputs: + params(GetOrderRequest): params required to query orders (e.g. symbol,statuses) + Returns: + list: a list of orders + """ + params = extract_enums(params,["symbol","statuses", "orderType"]) + + return await self.apis.get( + SERVICE_URLS["USER"]["ORDERS"], + params, + True + ) + + async def get_transaction_history(self,params:GetTransactionHistoryRequest): + """ + Returns a list of transaction. + Inputs: + params(GetTransactionHistoryRequest): params to query transactions (e.g. symbol) + Returns: + list: a list of transactions + """ + params = extract_enums(params,["symbol"]) + return await self.apis.get( + SERVICE_URLS["USER"]["USER_TRANSACTION_HISTORY"], + params, + True + ) + + async def get_user_position(self,params:GetPositionRequest): + """ + Returns a list of positions. + Inputs: + params(GetPositionRequest): params required to query positions (e.g. symbol) + Returns: + list: a list of positions + """ + params = extract_enums(params,["symbol"]) + return await self.apis.get( + SERVICE_URLS["USER"]["USER_POSITIONS"], + params, + True + ) + + async def get_user_trades(self,params:GetUserTradesRequest): + """ + Returns a list of user trades. + Inputs: + params(GetUserTradesRequest): params to query trades (e.g. symbol) + Returns: + list: a list of positions + """ + params = extract_enums(params,["symbol","type"]) + return await self.apis.get( + SERVICE_URLS["USER"]["USER_TRADES"], + params, + True + ) + + async def get_user_account_data(self, parentAddress:str = ""): + """ + Returns user account data. + Inputs: + parentAddress: an optional field, used by sub accounts to fetch parent account state + """ + return await self.apis.get( + service_url = SERVICE_URLS["USER"]["ACCOUNT"], + query = { "parentAddress": parentAddress }, + auth_required = True + ) + + async def get_user_leverage(self, symbol:MARKET_SYMBOLS, parentAddress:str=""): + """ + Returns user market default leverage. + Inputs: + symbol(MARKET_SYMBOLS): market symbol to get user market default leverage for. + parentAddress(str): an optional field, used by sub accounts to fetch parent account state + Returns: + str: user default leverage + """ + account_data_by_market = (await self.get_user_account_data(parentAddress))["accountDataByMarket"] + + for i in account_data_by_market: + if symbol.value==i["symbol"]: + return fromDapiBase(int(i["selectedLeverage"])) + # default leverage on system is 3 + # todo fetch from exchange info route + return 3 + + async def get_cancel_on_disconnect_timer(self, params:GetCancelOnDisconnectTimerRequest=None): + """ + Returns a list of the user's countDowns for provided market symbol, + Inputs: + - symbol(MARKET_SYMBOLS): (Optional) market symbol to get user market cancel_on_disconnect timer for, not providing it would return all the active countDown timers for each market. + - parentAddress (str):(Optional) Only provided by a sub account + Returns: + - GetCountDownsResponse: + - countDowns: object with provided market symbol and respective countDown timer + - timestamp + """ + + params = extract_enums(params, ["symbol"]) + response = await self.dmsApi.get( + SERVICE_URLS["USER"]["CANCEL_ON_DISCONNECT"], + params, + auth_required=True + ) + # check for service unavailibility + if hasattr(response, 'status') and response.status == 503: + raise Exception("Cancel on Disconnect (dead-mans-switch) feature is currently unavailable") + + return response + + async def reset_cancel_on_disconnect_timer(self, params:PostTimerAttributes): + """ + Returns PostTimerResponse containing accepted and failed countdowns, and the next page number + Inputs: + - params(PostTimerAttributes): params required to fetch funding history + Returns: + - PostTimerResponse: + - acceptedToReset: array with symbols for which timer was reset successfully + - failedReset: aray with symbols for whcih timer failed to reset + """ + response = await self.dmsApi.post( + SERVICE_URLS["USER"]["CANCEL_ON_DISCONNECT"], + json.dumps(params), + auth_required=True, + contentType = 'application/json' + ) + # check for service unavailibility + if hasattr(response, 'status') and response.status == 503: + raise Exception("Cancel on Disconnect (dead-mans-switch) feature is currently unavailable") + return response + + ## Internal methods + def _get_order_signer(self,symbol:MARKET_SYMBOLS=None): + """ + Returns the order signer for the specified symbol, else returns a dictionary of symbol -> order signer + Inputs: + symbol(MARKET_SYMBOLS): the symbol to get order signer for, optional + Returns: + dict/order signer object + """ + if symbol: + if symbol.value in self.order_signers.keys(): + return self.order_signers[symbol.value] + else: + raise(Exception("Signer does not exist. Make sure to add market")) + else: + return self.order_signers + + def _execute_tx(self, transaction): + """ + An internal function to create signed tx and wait for its receipt + Args: + transaction: A constructed txn using self.account address + + Returns: + tx_receipt: a receipt of txn mined on-chain + """ + tx_create = self.w3.eth.account.signTransaction(transaction, self.account.key) + tx_hash = self.w3.eth.sendRawTransaction(tx_create.rawTransaction) + return self.w3.eth.waitForTransactionReceipt(tx_hash) + + def _connect_w3(self,url): + """ + Creates a connection to Web3 RPC given the RPC url. + """ + try: + return Web3(Web3.HTTPProvider(url)) + except: + raise(Exception("Failed to connect to Host: {}".format(url))) + + + + async def close_connections(self): + # close aio http connection + await self.apis.close_session() + await self.dmsApi.close_session() + \ No newline at end of file diff --git a/src/bluefin_client_sui/constants.py b/src/bluefin_client_sui/constants.py new file mode 100644 index 0000000..610349d --- /dev/null +++ b/src/bluefin_client_sui/constants.py @@ -0,0 +1,137 @@ +Networks = { + "SUI_STAGING":{ + "url":"", + "chainId":1234, + "apiGateway":"https://dapi.api.sui-staging.bluefin.io", + "socketURL":"wss://dapi.api.sui-staging.bluefin.io", + "dmsURL":"https://dapi.api.sui-staging.bluefin.io", + "webSocketURL":"wss://notifications.api.sui-staging.bluefin.io", + "onboardingUrl": "https://testnet.bluefin.io", + + }, + + "DEV": { + "url": "https://l2-dev.firefly.exchange/", + "chainId": 78602, + "apiGateway": "https://dapi-dev.firefly.exchange", + "socketURL": "wss://dapi-dev.firefly.exchange", + "onboardingUrl": "https://dev.firefly.exchange", + }, + + "TESTNET_ARBITRUM": { + "url": "https://goerli-rollup.arbitrum.io/rpc", + "chainId": 421613, + "apiGateway": "https://dapi.api.arbitrum-staging.firefly.exchange", + "dmsURL": "https://api.arbitrum-staging.firefly.exchange/dead-man-switch", + "socketURL": "wss://dapi.api.arbitrum-staging.firefly.exchange", + "webSocketURL": "wss://notifications.api.arbitrum-staging.firefly.exchange", + "onboardingUrl": "https://testnet.firefly.exchange", + "UUID": "uuid-default" + }, + + "MAINNET_BOBA": { + "url": "https://bobabeam.boba.network/", + "chainId": 1294, + "apiGateway": "https://dapi.firefly.exchange", + "socketURL": "wss://dapi.firefly.exchange", + "onboardingUrl": "https://trade.firefly.exchange", + }, + "MAINNET_ARBITRUM": { + "url": "https://arb1.arbitrum.io/rpc/", + "chainId": 42161, + "apiGateway": "https://dapi.api.arbitrum-prod.firefly.exchange", + "dmsURL": "https://api.arbitrum-prod.firefly.exchange/dead-man-switch", + "socketURL": "wss://dapi.api.arbitrum-prod.firefly.exchange", + "webSocketURL": "wss://notifications.api.arbitrum-prod.firefly.exchange", + "onboardingUrl": "https://trade-arb.firefly.exchange", + "UUID": "uuid-default" + }, +} + + +EIP712_DOMAIN_NAME = "IsolatedTrader" + + +EIP712_DOMAIN_STRING = "EIP712Domain(string name,string version,uint128 chainId,address verifyingContract)" + + +EIP712_ORDER_STRUCT_STRING = \ + "Order(" + \ + "bytes8 flags," + \ + "uint128 quantity," + \ + "uint128 price," + \ + "uint128 triggerPrice," + \ + "uint128 leverage," + \ + "address maker," + \ + "uint128 expiration" + \ + ")" + +ORDER_FLAGS = { + "IS_BUY":1, + "IS_DECREASE_ONLY": 2 +} + +TIME = { + "SECONDS_IN_A_MINUTE": 60, + "SECONDS_IN_A_DAY": 86400, + "SECONDS_IN_A_MONTH": 2592000 +} + +ADDRESSES = { + "ZERO": "0x0000000000000000000000000000000000000000", +} + +SERVICE_URLS = { + "MARKET": { + "ORDER_BOOK": "/orderbook", + "RECENT_TRADE": "/recentTrades", + "CANDLE_STICK_DATA": "/candlestickData", + "EXCHANGE_INFO": "/exchangeInfo", + "MARKET_DATA": "/marketData", + "META": "/meta", + "STATUS": "/status", + "SYMBOLS": "/marketData/symbols", + "CONTRACT_ADDRESSES": "/marketData/contractAddresses", + "TICKER": "/ticker", + "MASTER_INFO": "/masterInfo", + "FUNDING_RATE":"/fundingRate" + }, + "USER": { + "USER_POSITIONS": "/userPosition", + "USER_TRADES": "/userTrades", + "ORDERS": "/orders", + "GENERATE_READONLY_TOKEN": "/generateReadOnlyToken", + "ACCOUNT": "/account", + "USER_TRANSACTION_HISTORY": "/userTransactionHistory", + "AUTHORIZE": "/authorize", + "ADJUST_LEVERAGE": "/account/adjustLeverage", + "FUND_GAS": "/account/fundGas", + "TRANSFER_HISTORY": "/userTransferHistory", + "FUNDING_HISTORY": "/userFundingHistory", + "CANCEL_ON_DISCONNECT": "/dms-countdown" + }, + "ORDERS": { + "ORDERS": "/orders", + "ORDERS_HASH": "/orders/hash", + }, +} + +EIP712_CANCEL_ORDER_STRUCT_STRING ="CancelLimitOrder(string action,bytes32[] orderHashes)" + +EIP712_ONBOARDING_ACTION_STRUCT_STRING = \ + 'firefly(' + \ + 'string action,' + \ + 'string onlySignOn' + \ + ')' + +EIP712_DOMAIN_STRING_NO_CONTRACT = \ + "EIP712Domain(" + \ + "string name," + \ + "string version," + \ + "uint128 chainId" + \ + ")" + + + +SUI_BASE_NUM=1000000000 +DAPI_BASE_NUM=1000000000000000000 \ No newline at end of file diff --git a/src/bluefin_client_sui/contract_abis.py b/src/bluefin_client_sui/contract_abis.py new file mode 100644 index 0000000..ed0d791 --- /dev/null +++ b/src/bluefin_client_sui/contract_abis.py @@ -0,0 +1,2116 @@ +## ABIs of contracts to be used by client + +MarginBank = { + "_format": "hh-sol-artifact-1", + "contractName": "MarginBank", + "sourceName": "contracts/MarginBank.sol", + "abi": [ + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "enum MarginBank.Action", + "name": "action", + "type": "uint8" + }, + { + "indexed": True, + "internalType": "address", + "name": "srcAddress", + "type": "address" + }, + { + "indexed": True, + "internalType": "address", + "name": "destAddress", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "srcBalance", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "destBalance", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "timestamp", + "type": "uint128" + } + ], + "name": "BankBalanceUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": False, + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "balance", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "timestamp", + "type": "uint128" + } + ], + "name": "BankTransferToPerpetual", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": False, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "MarginBankOperatorUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": True, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "bankBalances", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "bankOperators", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "candidate", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + }, + { + "internalType": "uint128", + "name": "_amount", + "type": "uint128" + } + ], + "name": "depositToBank", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "getAccountBankBalance", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "guardianContract", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_guardian", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isWithdrawalAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "_approved", + "type": "bool" + } + ], + "name": "setBankOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum Types.GuardianStatus", + "name": "_newStatus", + "type": "uint8" + } + ], + "name": "setWithdrawalStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + }, + { + "internalType": "address", + "name": "_destination", + "type": "address" + }, + { + "internalType": "uint128", + "name": "_amount", + "type": "uint128" + } + ], + "name": "transferMarginToAccount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "updateOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_destination", + "type": "address" + }, + { + "internalType": "uint128", + "name": "_amount", + "type": "uint128" + } + ], + "name": "withdrawFromBank", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50611946806100206000396000f3fe608060405234801561001057600080fd5b506004361061010b5760003560e01c80637b1657c7116100a257806397a5e1441161007157806397a5e1441461025d5780639aacb467146102655780639ff5c88914610288578063bc5920ba1461029b578063fc0c546a146102a357600080fd5b80637b1657c7146101c65780637c7a7596146101d95780638b1681a6146102235780638da5cb5b1461024c57600080fd5b8063419dd428116100de578063419dd42814610173578063485cc955146101865780636c8381f814610199578063715018a6146101be57600080fd5b8063053834ee1461011057806313af4035146101385780632bce5f251461014d5780632c428e4e14610160575b600080fd5b61012361011e3660046115ad565b6102b6565b60405190151581526020015b60405180910390f35b61014b6101463660046114e9565b61040d565b005b61014b61015b366004611535565b610595565b61014b61016e3660046115f2565b6107b1565b61014b6101813660046115ad565b61088a565b61014b610194366004611503565b610a23565b6066546001600160a01b03165b6040516001600160a01b03909116815260200161012f565b61014b610b27565b61014b6101d4366004611577565b610ba7565b61020b6101e73660046114e9565b6001600160a01b0316600090815260cd60205260409020546001600160801b031690565b6040516001600160801b03909116815260200161012f565b61020b6102313660046114e9565b60cd602052600090815260409020546001600160801b031681565b6065546001600160a01b03166101a6565b610123610c60565b6101236102733660046114e9565b60cc6020526000908152604090205460ff1681565b60ce546101a6906001600160a01b031681565b61014b610c95565b60cb546101a6906001600160a01b031681565b6000600260015414156102e45760405162461bcd60e51b81526004016102db906117c6565b60405180910390fd5b600260015560cb5461030a906001600160a01b03163330856001600160801b0316610dd5565b6103198264e8d4a51000611837565b6001600160a01b038416600090815260cd60205260408120805490919061034a9084906001600160801b03166117fd565b92506101000a8154816001600160801b0302191690836001600160801b03160217905550826001600160a01b031661037f3390565b6001600160a01b03167f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f60006103ba8664e8d4a51000611837565b33600090815260cd6020526040808220546001600160a01b038b168352918190205490516103f99493926001600160801b03908116921690429061165c565b60405180910390a350600180805592915050565b6065546001600160a01b031633146104375760405162461bcd60e51b81526004016102db906116e0565b6001600160a01b0381166104985760405162461bcd60e51b815260206004820152602260248201527f46464c5946694f776e61626c65557067726164653a207a65726f206164647265604482015261737360f01b60648201526084016102db565b6065546001600160a01b03828116911614156105055760405162461bcd60e51b815260206004820152602660248201527f46464c5946694f776e61626c65557067726164653a2073616d65206173206f726044820152651a59da5b985b60d21b60648201526084016102db565b6066546001600160a01b03828116911614156105735760405162461bcd60e51b815260206004820152602760248201527f46464c5946694f776e61626c65557067726164653a2073616d652061732063616044820152666e64696461746560c81b60648201526084016102db565b606680546001600160a01b0319166001600160a01b0392909216919091179055565b600260015414156105b85760405162461bcd60e51b81526004016102db906117c6565b600260015533600090815260cc602052604090205460ff1661062e5760405162461bcd60e51b815260206004820152602960248201527f4d617267696e42616e6b3a2063616c6c6572206973206e6f7420612062616e6b6044820152681037b832b930ba37b960b91b60648201526084016102db565b6001600160a01b038316600090815260cd6020908152604091829020548251808401909352601a83527f496e73756666696369656e74206163636f756e742066756e64730000000000009183019190915261069c916001600160801b03808516921691909110159085610e46565b6001600160a01b038316600090815260cd6020526040812080548392906106cd9084906001600160801b0316611866565b82546101009290920a6001600160801b038181021990931691831602179091556001600160a01b038416600090815260cd6020526040812080548594509092610718918591166117fd565b82546101009290920a6001600160801b038181021990931691831602179091556001600160a01b03858116600081815260cd60205260408082205493881680835291205490945090927f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f9260029287929182169116425b6040516107a095949392919061165c565b60405180910390a350506001805550565b600260015414156107d45760405162461bcd60e51b81526004016102db906117c6565b600260015560ce546001600160a01b0316331461084b5760405162461bcd60e51b815260206004820152602f60248201527f4d617267696e42616e6b3a2063616c6c6572206973206e6f742074686520677560448201526e185c991a585b8818dbdb9d1c9858dd608a1b60648201526084016102db565b60ce805482919060ff60a01b1916600160a01b83600181111561087e57634e487b7160e01b600052602160045260246000fd5b02179055505060018055565b600260015414156108ad5760405162461bcd60e51b81526004016102db906117c6565b6002600155336108bb610c60565b6109215760405162461bcd60e51b815260206004820152603160248201527f4d617267696e42616e6b3a205769746864726177616c73206e6f7420616c6c6f6044820152701dd95908185d081d1a19481b5bdb595b9d607a1b60648201526084016102db565b60cb54610941906001600160a01b0316846001600160801b038516610e8c565b6109508264e8d4a51000611837565b6001600160a01b038216600090815260cd6020526040812080549091906109819084906001600160801b0316611866565b92506101000a8154816001600160801b0302191690836001600160801b03160217905550826001600160a01b0316816001600160a01b03167f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f60018564e8d4a510006109ed9190611837565b6001600160a01b03868116600090815260cd602052604080822054928b1682529020546001600160801b0391821691164261078f565b600054610100900460ff1615808015610a435750600054600160ff909116105b80610a5d5750303b158015610a5d575060005460ff166001145b610a795760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015610a9c576000805461ff0019166101001790555b60cb80546001600160a01b038086166001600160a01b03199283161790925560ce805492851692909116919091179055610ad4610ebc565b610adc610f8f565b8015610b22576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b6065546001600160a01b03163314610b515760405162461bcd60e51b81526004016102db906116e0565b6065546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3606580546001600160a01b0319908116909155606680549091169055565b6065546001600160a01b03163314610bd15760405162461bcd60e51b81526004016102db906116e0565b60026001541415610bf45760405162461bcd60e51b81526004016102db906117c6565b60026001556001600160a01b038216600081815260cc6020908152604091829020805460ff19168515159081179091558251938452908301527f23a3d102fca7a9f251ba156fa3583ea391662039e1e0279b951ebd17dbf58bc3910160405180910390a1505060018055565b60008060ce54600160a01b900460ff166001811115610c8f57634e487b7160e01b600052602160045260246000fd5b14905090565b6066546001600160a01b0316610d055760405162461bcd60e51b815260206004820152602f60248201527f46464c5946694f776e61626c65557067726164653a2063616e6469646174652060448201526e6973207a65726f206164647265737360881b60648201526084016102db565b6066546001600160a01b03163314610d6f5760405162461bcd60e51b815260206004820152602760248201527f46464c5946694f776e61626c65557067726164653a206e6f7420746865206e656044820152663b9037bbb732b960c91b60648201526084016102db565b6066546065546040516001600160a01b0392831692909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a360668054606580546001600160a01b03199081166001600160a01b03841617909155169055565b6040516001600160a01b0380851660248301528316604482015260648101829052610e409085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610fc0565b50505050565b82610b225781610e5582611092565b604051602001610e6692919061162d565b60408051601f198184030181529082905262461bcd60e51b82526102db916004016116ad565b6040516001600160a01b038316602482015260448101829052610b2290849063a9059cbb60e01b90606401610e09565b600054610100900460ff1615808015610edc5750600054600160ff909116105b80610ef65750303b158015610ef6575060005460ff166001145b610f125760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015610f35576000805461ff0019166101001790555b610f3d6111b5565b610f456111dc565b8015610f8c576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15b50565b600054610100900460ff16610fb65760405162461bcd60e51b81526004016102db9061177b565b610fbe6112da565b565b6000611015826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166113079092919063ffffffff16565b805190915015610b22578080602001905181019061103391906115d6565b610b225760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016102db565b6040517f3a20307830303030303030302e2e2e3030303030303030000000000000000000602082015260609060009060370160408051601f1981840301815291905290506001600160a01b03831660a0602060045b600c8110156111aa576110fb60048461188e565b925061110860048361188e565b915061111584841c611320565b85828151811061113557634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a90535061115784831c611320565b8561116383600b61181f565b8151811061118157634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a905350806111a2816118d1565b9150506110e7565b509295945050505050565b600054610100900460ff16610fbe5760405162461bcd60e51b81526004016102db9061177b565b600054610100900460ff16158080156111fc5750600054600160ff909116105b806112165750303b158015611216575060005460ff166001145b6112325760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015611255576000805461ff0019166101001790555b606580546001600160a01b0319163390811790915560405181906000907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a3508015610f8c576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb384740249890602001610f83565b600054610100900460ff166113015760405162461bcd60e51b81526004016102db9061177b565b60018055565b6060611316848460008561134c565b90505b9392505050565b6000600f8216600a8110611335576057611338565b60305b611342908261181f565b60f81b9392505050565b6060824710156113ad5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016102db565b6001600160a01b0385163b6114045760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016102db565b600080866001600160a01b031685876040516114209190611611565b60006040518083038185875af1925050503d806000811461145d576040519150601f19603f3d011682016040523d82523d6000602084013e611462565b606091505b509150915061147282828661147d565b979650505050505050565b6060831561148c575081611319565b82511561149c5782518084602001fd5b8160405162461bcd60e51b81526004016102db91906116ad565b80356001600160a01b03811681146114cd57600080fd5b919050565b80356001600160801b03811681146114cd57600080fd5b6000602082840312156114fa578081fd5b611319826114b6565b60008060408385031215611515578081fd5b61151e836114b6565b915061152c602084016114b6565b90509250929050565b600080600060608486031215611549578081fd5b611552846114b6565b9250611560602085016114b6565b915061156e604085016114d2565b90509250925092565b60008060408385031215611589578182fd5b611592836114b6565b915060208301356115a281611902565b809150509250929050565b600080604083850312156115bf578182fd5b6115c8836114b6565b915061152c602084016114d2565b6000602082840312156115e7578081fd5b815161131981611902565b600060208284031215611603578081fd5b813560028110611319578182fd5b600082516116238184602087016118a5565b9190910192915050565b6000835161163f8184602088016118a5565b8351908301906116538183602088016118a5565b01949350505050565b60a081016003871061167e57634e487b7160e01b600052602160045260246000fd5b9581526001600160801b0394851660208201529284166040840152908316606083015290911660809091015290565b60208152600082518060208401526116cc8160408501602087016118a5565b601f01601f19169190910160400192915050565b6020808252602d908201527f46464c5946694f776e61626c65557067726164653a2063616c6c65722069732060408201526c3737ba103a34329037bbb732b960991b606082015260800190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60006001600160801b03808316818516808303821115611653576116536118ec565b60008219821115611832576118326118ec565b500190565b60006001600160801b038083168185168183048111821515161561185d5761185d6118ec565b02949350505050565b60006001600160801b0383811690831681811015611886576118866118ec565b039392505050565b6000828210156118a0576118a06118ec565b500390565b60005b838110156118c05781810151838201526020016118a8565b83811115610e405750506000910152565b60006000198214156118e5576118e56118ec565b5060010190565b634e487b7160e01b600052601160045260246000fd5b8015158114610f8c57600080fdfea26469706673582212202ba8a44886c5de0596cc4ec7928626d84ae6af7670c4e7feb4666098a9bb9f9964736f6c63430008040033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061010b5760003560e01c80637b1657c7116100a257806397a5e1441161007157806397a5e1441461025d5780639aacb467146102655780639ff5c88914610288578063bc5920ba1461029b578063fc0c546a146102a357600080fd5b80637b1657c7146101c65780637c7a7596146101d95780638b1681a6146102235780638da5cb5b1461024c57600080fd5b8063419dd428116100de578063419dd42814610173578063485cc955146101865780636c8381f814610199578063715018a6146101be57600080fd5b8063053834ee1461011057806313af4035146101385780632bce5f251461014d5780632c428e4e14610160575b600080fd5b61012361011e3660046115ad565b6102b6565b60405190151581526020015b60405180910390f35b61014b6101463660046114e9565b61040d565b005b61014b61015b366004611535565b610595565b61014b61016e3660046115f2565b6107b1565b61014b6101813660046115ad565b61088a565b61014b610194366004611503565b610a23565b6066546001600160a01b03165b6040516001600160a01b03909116815260200161012f565b61014b610b27565b61014b6101d4366004611577565b610ba7565b61020b6101e73660046114e9565b6001600160a01b0316600090815260cd60205260409020546001600160801b031690565b6040516001600160801b03909116815260200161012f565b61020b6102313660046114e9565b60cd602052600090815260409020546001600160801b031681565b6065546001600160a01b03166101a6565b610123610c60565b6101236102733660046114e9565b60cc6020526000908152604090205460ff1681565b60ce546101a6906001600160a01b031681565b61014b610c95565b60cb546101a6906001600160a01b031681565b6000600260015414156102e45760405162461bcd60e51b81526004016102db906117c6565b60405180910390fd5b600260015560cb5461030a906001600160a01b03163330856001600160801b0316610dd5565b6103198264e8d4a51000611837565b6001600160a01b038416600090815260cd60205260408120805490919061034a9084906001600160801b03166117fd565b92506101000a8154816001600160801b0302191690836001600160801b03160217905550826001600160a01b031661037f3390565b6001600160a01b03167f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f60006103ba8664e8d4a51000611837565b33600090815260cd6020526040808220546001600160a01b038b168352918190205490516103f99493926001600160801b03908116921690429061165c565b60405180910390a350600180805592915050565b6065546001600160a01b031633146104375760405162461bcd60e51b81526004016102db906116e0565b6001600160a01b0381166104985760405162461bcd60e51b815260206004820152602260248201527f46464c5946694f776e61626c65557067726164653a207a65726f206164647265604482015261737360f01b60648201526084016102db565b6065546001600160a01b03828116911614156105055760405162461bcd60e51b815260206004820152602660248201527f46464c5946694f776e61626c65557067726164653a2073616d65206173206f726044820152651a59da5b985b60d21b60648201526084016102db565b6066546001600160a01b03828116911614156105735760405162461bcd60e51b815260206004820152602760248201527f46464c5946694f776e61626c65557067726164653a2073616d652061732063616044820152666e64696461746560c81b60648201526084016102db565b606680546001600160a01b0319166001600160a01b0392909216919091179055565b600260015414156105b85760405162461bcd60e51b81526004016102db906117c6565b600260015533600090815260cc602052604090205460ff1661062e5760405162461bcd60e51b815260206004820152602960248201527f4d617267696e42616e6b3a2063616c6c6572206973206e6f7420612062616e6b6044820152681037b832b930ba37b960b91b60648201526084016102db565b6001600160a01b038316600090815260cd6020908152604091829020548251808401909352601a83527f496e73756666696369656e74206163636f756e742066756e64730000000000009183019190915261069c916001600160801b03808516921691909110159085610e46565b6001600160a01b038316600090815260cd6020526040812080548392906106cd9084906001600160801b0316611866565b82546101009290920a6001600160801b038181021990931691831602179091556001600160a01b038416600090815260cd6020526040812080548594509092610718918591166117fd565b82546101009290920a6001600160801b038181021990931691831602179091556001600160a01b03858116600081815260cd60205260408082205493881680835291205490945090927f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f9260029287929182169116425b6040516107a095949392919061165c565b60405180910390a350506001805550565b600260015414156107d45760405162461bcd60e51b81526004016102db906117c6565b600260015560ce546001600160a01b0316331461084b5760405162461bcd60e51b815260206004820152602f60248201527f4d617267696e42616e6b3a2063616c6c6572206973206e6f742074686520677560448201526e185c991a585b8818dbdb9d1c9858dd608a1b60648201526084016102db565b60ce805482919060ff60a01b1916600160a01b83600181111561087e57634e487b7160e01b600052602160045260246000fd5b02179055505060018055565b600260015414156108ad5760405162461bcd60e51b81526004016102db906117c6565b6002600155336108bb610c60565b6109215760405162461bcd60e51b815260206004820152603160248201527f4d617267696e42616e6b3a205769746864726177616c73206e6f7420616c6c6f6044820152701dd95908185d081d1a19481b5bdb595b9d607a1b60648201526084016102db565b60cb54610941906001600160a01b0316846001600160801b038516610e8c565b6109508264e8d4a51000611837565b6001600160a01b038216600090815260cd6020526040812080549091906109819084906001600160801b0316611866565b92506101000a8154816001600160801b0302191690836001600160801b03160217905550826001600160a01b0316816001600160a01b03167f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f60018564e8d4a510006109ed9190611837565b6001600160a01b03868116600090815260cd602052604080822054928b1682529020546001600160801b0391821691164261078f565b600054610100900460ff1615808015610a435750600054600160ff909116105b80610a5d5750303b158015610a5d575060005460ff166001145b610a795760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015610a9c576000805461ff0019166101001790555b60cb80546001600160a01b038086166001600160a01b03199283161790925560ce805492851692909116919091179055610ad4610ebc565b610adc610f8f565b8015610b22576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b6065546001600160a01b03163314610b515760405162461bcd60e51b81526004016102db906116e0565b6065546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3606580546001600160a01b0319908116909155606680549091169055565b6065546001600160a01b03163314610bd15760405162461bcd60e51b81526004016102db906116e0565b60026001541415610bf45760405162461bcd60e51b81526004016102db906117c6565b60026001556001600160a01b038216600081815260cc6020908152604091829020805460ff19168515159081179091558251938452908301527f23a3d102fca7a9f251ba156fa3583ea391662039e1e0279b951ebd17dbf58bc3910160405180910390a1505060018055565b60008060ce54600160a01b900460ff166001811115610c8f57634e487b7160e01b600052602160045260246000fd5b14905090565b6066546001600160a01b0316610d055760405162461bcd60e51b815260206004820152602f60248201527f46464c5946694f776e61626c65557067726164653a2063616e6469646174652060448201526e6973207a65726f206164647265737360881b60648201526084016102db565b6066546001600160a01b03163314610d6f5760405162461bcd60e51b815260206004820152602760248201527f46464c5946694f776e61626c65557067726164653a206e6f7420746865206e656044820152663b9037bbb732b960c91b60648201526084016102db565b6066546065546040516001600160a01b0392831692909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a360668054606580546001600160a01b03199081166001600160a01b03841617909155169055565b6040516001600160a01b0380851660248301528316604482015260648101829052610e409085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610fc0565b50505050565b82610b225781610e5582611092565b604051602001610e6692919061162d565b60408051601f198184030181529082905262461bcd60e51b82526102db916004016116ad565b6040516001600160a01b038316602482015260448101829052610b2290849063a9059cbb60e01b90606401610e09565b600054610100900460ff1615808015610edc5750600054600160ff909116105b80610ef65750303b158015610ef6575060005460ff166001145b610f125760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015610f35576000805461ff0019166101001790555b610f3d6111b5565b610f456111dc565b8015610f8c576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15b50565b600054610100900460ff16610fb65760405162461bcd60e51b81526004016102db9061177b565b610fbe6112da565b565b6000611015826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166113079092919063ffffffff16565b805190915015610b22578080602001905181019061103391906115d6565b610b225760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016102db565b6040517f3a20307830303030303030302e2e2e3030303030303030000000000000000000602082015260609060009060370160408051601f1981840301815291905290506001600160a01b03831660a0602060045b600c8110156111aa576110fb60048461188e565b925061110860048361188e565b915061111584841c611320565b85828151811061113557634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a90535061115784831c611320565b8561116383600b61181f565b8151811061118157634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a905350806111a2816118d1565b9150506110e7565b509295945050505050565b600054610100900460ff16610fbe5760405162461bcd60e51b81526004016102db9061177b565b600054610100900460ff16158080156111fc5750600054600160ff909116105b806112165750303b158015611216575060005460ff166001145b6112325760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015611255576000805461ff0019166101001790555b606580546001600160a01b0319163390811790915560405181906000907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a3508015610f8c576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb384740249890602001610f83565b600054610100900460ff166113015760405162461bcd60e51b81526004016102db9061177b565b60018055565b6060611316848460008561134c565b90505b9392505050565b6000600f8216600a8110611335576057611338565b60305b611342908261181f565b60f81b9392505050565b6060824710156113ad5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016102db565b6001600160a01b0385163b6114045760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016102db565b600080866001600160a01b031685876040516114209190611611565b60006040518083038185875af1925050503d806000811461145d576040519150601f19603f3d011682016040523d82523d6000602084013e611462565b606091505b509150915061147282828661147d565b979650505050505050565b6060831561148c575081611319565b82511561149c5782518084602001fd5b8160405162461bcd60e51b81526004016102db91906116ad565b80356001600160a01b03811681146114cd57600080fd5b919050565b80356001600160801b03811681146114cd57600080fd5b6000602082840312156114fa578081fd5b611319826114b6565b60008060408385031215611515578081fd5b61151e836114b6565b915061152c602084016114b6565b90509250929050565b600080600060608486031215611549578081fd5b611552846114b6565b9250611560602085016114b6565b915061156e604085016114d2565b90509250925092565b60008060408385031215611589578182fd5b611592836114b6565b915060208301356115a281611902565b809150509250929050565b600080604083850312156115bf578182fd5b6115c8836114b6565b915061152c602084016114d2565b6000602082840312156115e7578081fd5b815161131981611902565b600060208284031215611603578081fd5b813560028110611319578182fd5b600082516116238184602087016118a5565b9190910192915050565b6000835161163f8184602088016118a5565b8351908301906116538183602088016118a5565b01949350505050565b60a081016003871061167e57634e487b7160e01b600052602160045260246000fd5b9581526001600160801b0394851660208201529284166040840152908316606083015290911660809091015290565b60208152600082518060208401526116cc8160408501602087016118a5565b601f01601f19169190910160400192915050565b6020808252602d908201527f46464c5946694f776e61626c65557067726164653a2063616c6c65722069732060408201526c3737ba103a34329037bbb732b960991b606082015260800190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60006001600160801b03808316818516808303821115611653576116536118ec565b60008219821115611832576118326118ec565b500190565b60006001600160801b038083168185168183048111821515161561185d5761185d6118ec565b02949350505050565b60006001600160801b0383811690831681811015611886576118866118ec565b039392505050565b6000828210156118a0576118a06118ec565b500390565b60005b838110156118c05781810151838201526020016118a8565b83811115610e405750506000910152565b60006000198214156118e5576118e56118ec565b5060010190565b634e487b7160e01b600052601160045260246000fd5b8015158114610f8c57600080fdfea26469706673582212202ba8a44886c5de0596cc4ec7928626d84ae6af7670c4e7feb4666098a9bb9f9964736f6c63430008040033", + "linkReferences": {}, + "deployedLinkReferences": {} +} + +Perpetual = { + "_format": "hh-sol-artifact-1", + "contractName": "Perpetual", + "sourceName": "contracts/Perpetual.sol", + "abi": [ + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": False, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "components": [ + { + "internalType": "bool", + "name": "isPosPositive", + "type": "bool" + }, + { + "internalType": "uint128", + "name": "mro", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "qPos", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "margin", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "oiOpen", + "type": "uint128" + } + ], + "indexed": False, + "internalType": "struct Types.PositionBalance", + "name": "position", + "type": "tuple" + }, + { + "indexed": False, + "internalType": "enum Perpetual.Action", + "name": "action", + "type": "uint8" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "timestamp", + "type": "uint128" + } + ], + "name": "AccountPositionUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "components": [ + { + "internalType": "bool", + "name": "isPosPositive", + "type": "bool" + }, + { + "internalType": "uint128", + "name": "mro", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "qPos", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "margin", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "oiOpen", + "type": "uint128" + } + ], + "indexed": False, + "internalType": "struct Types.PositionBalance", + "name": "balance", + "type": "tuple" + }, + { + "indexed": False, + "internalType": "bool", + "name": "settlementIsPositive", + "type": "bool" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "settlementAmount", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "price", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "int256", + "name": "fundingRate", + "type": "int256" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "timestamp", + "type": "uint128" + } + ], + "name": "AccountSettlementUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "uint128", + "name": "defaultMakerFee", + "type": "uint128" + } + ], + "name": "DefaultMakerFeeUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "uint128", + "name": "defaultTakerFee", + "type": "uint128" + } + ], + "name": "DefaultTakerFeeUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "feePool", + "type": "address" + } + ], + "name": "FeePoolUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "funder", + "type": "address" + } + ], + "name": "FundingOracleUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "int256", + "name": "", + "type": "int256" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "name": "GlobalIndexUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "int256", + "name": "initialMargin", + "type": "int256" + } + ], + "name": "IMRUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "int256", + "name": "maintenanceMargin", + "type": "int256" + } + ], + "name": "MMRUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "marginBank", + "type": "address" + } + ], + "name": "MarginBankUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "provider", + "type": "address" + } + ], + "name": "OffchainFROperatorUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": True, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "uint128", + "name": "settlementPrice", + "type": "uint128" + } + ], + "name": "PerpetualDelisted", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "timestamp", + "type": "uint128" + } + ], + "name": "PositionClosed", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "PriceOracleUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": False, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "SettlementOperatorUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": False, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": False, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "SubAccountUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "tradeContract", + "type": "address" + }, + { + "indexed": False, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "TradeContractUpdate", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": True, + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "indexed": True, + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "indexed": False, + "internalType": "bytes32", + "name": "makerOrderHash", + "type": "bytes32" + }, + { + "indexed": False, + "internalType": "bytes32", + "name": "takerOrderHash", + "type": "bytes32" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "makerMRO", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "takerMRO", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "makerFee", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "takerFee", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "int256", + "name": "makerPnl", + "type": "int256" + }, + { + "indexed": False, + "internalType": "int256", + "name": "takerPnl", + "type": "int256" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "tradeQuantity", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "price", + "type": "uint128" + }, + { + "indexed": False, + "internalType": "bool", + "name": "isBuy", + "type": "bool" + }, + { + "indexed": False, + "internalType": "uint128", + "name": "timestamp", + "type": "uint128" + } + ], + "name": "TradeExecuted", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "uint128", + "name": "startTime", + "type": "uint128" + } + ], + "name": "TradingStarted", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "uint128", + "name": "stopTime", + "type": "uint128" + } + ], + "name": "TradingStopped", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + }, + { + "internalType": "uint128", + "name": "_amount", + "type": "uint128" + } + ], + "name": "addMargin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "addresses", + "outputs": [ + { + "internalType": "address", + "name": "marginMath", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "funder", + "type": "address" + }, + { + "internalType": "address", + "name": "marginBank", + "type": "address" + }, + { + "internalType": "address", + "name": "evaluator", + "type": "address" + }, + { + "internalType": "address", + "name": "feePool", + "type": "address" + }, + { + "internalType": "address", + "name": "guardian", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + }, + { + "internalType": "uint128", + "name": "_leverage", + "type": "uint128" + } + ], + "name": "adjustLeverage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "candidate", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "closePosition", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "defaultMakerFee", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "defaultTakerFee", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "_priceLowerBound", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "_priceUpperBound", + "type": "uint128" + } + ], + "name": "delistPerpetual", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "delisted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "delistingPrice", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAccountBalance", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "isPosPositive", + "type": "bool" + }, + { + "internalType": "uint128", + "name": "mro", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "qPos", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "margin", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "oiOpen", + "type": "uint128" + } + ], + "internalType": "struct Types.PositionBalance", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "getIsSubAccount", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "globalIndex", + "outputs": [ + { + "internalType": "uint128", + "name": "timestamp", + "type": "uint128" + }, + { + "internalType": "int256", + "name": "value", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "hasAccountPermissions", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialMarginRequired", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_marketName", + "type": "string" + }, + { + "components": [ + { + "internalType": "address", + "name": "marginMath", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "funder", + "type": "address" + }, + { + "internalType": "address", + "name": "marginBank", + "type": "address" + }, + { + "internalType": "address", + "name": "evaluator", + "type": "address" + }, + { + "internalType": "address", + "name": "feePool", + "type": "address" + }, + { + "internalType": "address", + "name": "guardian", + "type": "address" + } + ], + "internalType": "struct IPerpetual.Addresses", + "name": "_addresses", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_trustedForwarder", + "type": "address" + }, + { + "internalType": "int256", + "name": "_initialMargin", + "type": "int256" + }, + { + "internalType": "int256", + "name": "_maintenanceMargin", + "type": "int256" + }, + { + "internalType": "uint128", + "name": "_defaultMakerFee", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "_defaultTakerFee", + "type": "uint128" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isTradingAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "forwarder", + "type": "address" + } + ], + "name": "isTrustedForwarder", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maintenanceMarginRequired", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "marketName", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "msgSender", + "outputs": [ + { + "internalType": "address payable", + "name": "ret", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + }, + { + "internalType": "uint128", + "name": "_amount", + "type": "uint128" + } + ], + "name": "removeMargin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "_defaultMakerFee", + "type": "uint128" + } + ], + "name": "setDefaultMakerFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "_defaultTakerFee", + "type": "uint128" + } + ], + "name": "setDefaultTakerFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_feePool", + "type": "address" + } + ], + "name": "setFeePool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_funder", + "type": "address" + } + ], + "name": "setFunder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "setFundingRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "_initialMargin", + "type": "int256" + } + ], + "name": "setInitialMargin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "_maintenanceMargin", + "type": "int256" + } + ], + "name": "setMaintenanceMargin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_marginBank", + "type": "address" + } + ], + "name": "setMarginBank", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newOperator", + "type": "address" + } + ], + "name": "setOffChainFROperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "offchainFundingRate", + "type": "int256" + } + ], + "name": "setOffChainFundingRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_oracle", + "type": "address" + } + ], + "name": "setOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "_approved", + "type": "bool" + } + ], + "name": "setSettlementOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "_approved", + "type": "bool" + } + ], + "name": "setSubAccount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_trader", + "type": "address" + }, + { + "internalType": "bool", + "name": "_approved", + "type": "bool" + } + ], + "name": "setTradeContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum Types.GuardianStatus", + "name": "_newStatus", + "type": "uint8" + } + ], + "name": "setTradingStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "settlementOperators", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "_startingTime", + "type": "uint128" + } + ], + "name": "startTrading", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stopTrading", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_accounts", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "takerIndex", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "makerIndex", + "type": "uint128" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IPerpetual.TradeArg[]", + "name": "_trades", + "type": "tuple[]" + }, + { + "internalType": "uint128", + "name": "gasCharges", + "type": "uint128" + } + ], + "name": "trade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tradeContracts", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tradingStartTime", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "trustedForwarder", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "updateOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} + +USDC = { + "_format": "hh-sol-artifact-1", + "contractName": "DummyUSDC", + "sourceName": "contracts/mock/DummyUSDC.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "uint128", + "name": "_initialSupply", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": True, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": True, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "_amount", + "type": "uint128" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x60806040523480156200001157600080fd5b5060405162000db238038062000db28339810160408190526200003491620002cb565b8251839083906200004d90600390602085019062000172565b5080516200006390600490602084019062000172565b5050506200008133826001600160801b03166200008a60201b60201c565b505050620003cc565b6001600160a01b038216620000e55760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015260640160405180910390fd5b8060026000828254620000f9919062000354565b90915550506001600160a01b038216600090815260208190526040812080548392906200012890849062000354565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b828054620001809062000379565b90600052602060002090601f016020900481019282620001a45760008555620001ef565b82601f10620001bf57805160ff1916838001178555620001ef565b82800160010185558215620001ef579182015b82811115620001ef578251825591602001919060010190620001d2565b50620001fd92915062000201565b5090565b5b80821115620001fd576000815560010162000202565b600082601f83011262000229578081fd5b81516001600160401b0380821115620002465762000246620003b6565b604051601f8301601f19908116603f01168101908282118183101715620002715762000271620003b6565b816040528381526020925086838588010111156200028d578485fd5b8491505b83821015620002b0578582018301518183018401529082019062000291565b83821115620002c157848385830101525b9695505050505050565b600080600060608486031215620002e0578283fd5b83516001600160401b0380821115620002f7578485fd5b620003058783880162000218565b945060208601519150808211156200031b578384fd5b506200032a8682870162000218565b604086015190935090506001600160801b038116811462000349578182fd5b809150509250925092565b600082198211156200037457634e487b7160e01b81526011600452602481fd5b500190565b600181811c908216806200038e57607f821691505b60208210811415620003b057634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052604160045260246000fd5b6109d680620003dc6000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c806370a082311161007157806370a082311461014157806395d89b411461016a578063a457c2d714610172578063a9059cbb14610185578063be29184f14610198578063dd62ed3e146101ad57600080fd5b806306fdde03146100b9578063095ea7b3146100d757806318160ddd146100fa57806323b872dd1461010c578063313ce5671461011f578063395093511461012e575b600080fd5b6100c16101c0565b6040516100ce91906108ee565b60405180910390f35b6100ea6100e53660046108c5565b610252565b60405190151581526020016100ce565b6002545b6040519081526020016100ce565b6100ea61011a366004610849565b61026a565b604051600681526020016100ce565b6100ea61013c3660046108c5565b61028e565b6100fe61014f3660046107f6565b6001600160a01b031660009081526020819052604090205490565b6100c16102b0565b6100ea6101803660046108c5565b6102bf565b6100ea6101933660046108c5565b61033f565b6101ab6101a6366004610884565b61034d565b005b6100fe6101bb366004610817565b610364565b6060600380546101cf90610965565b80601f01602080910402602001604051908101604052809291908181526020018280546101fb90610965565b80156102485780601f1061021d57610100808354040283529160200191610248565b820191906000526020600020905b81548152906001019060200180831161022b57829003601f168201915b5050505050905090565b60003361026081858561038f565b5060019392505050565b6000336102788582856104b3565b61028385858561052d565b506001949350505050565b6000336102608185856102a18383610364565b6102ab9190610941565b61038f565b6060600480546101cf90610965565b600033816102cd8286610364565b9050838110156103325760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b610283828686840361038f565b60003361026081858561052d565b61036082826001600160801b03166106fb565b5050565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103f15760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610329565b6001600160a01b0382166104525760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610329565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104bf8484610364565b90506000198114610527578181101561051a5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610329565b610527848484840361038f565b50505050565b6001600160a01b0383166105915760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610329565b6001600160a01b0382166105f35760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610329565b6001600160a01b0383166000908152602081905260409020548181101561066b5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610329565b6001600160a01b038085166000908152602081905260408082208585039055918516815290812080548492906106a2908490610941565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516106ee91815260200190565b60405180910390a3610527565b6001600160a01b0382166107515760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610329565b80600260008282546107639190610941565b90915550506001600160a01b03821660009081526020819052604081208054839290610790908490610941565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b80356001600160a01b03811681146107f157600080fd5b919050565b600060208284031215610807578081fd5b610810826107da565b9392505050565b60008060408385031215610829578081fd5b610832836107da565b9150610840602084016107da565b90509250929050565b60008060006060848603121561085d578081fd5b610866846107da565b9250610874602085016107da565b9150604084013590509250925092565b60008060408385031215610896578182fd5b61089f836107da565b915060208301356001600160801b03811681146108ba578182fd5b809150509250929050565b600080604083850312156108d7578182fd5b6108e0836107da565b946020939093013593505050565b6000602080835283518082850152825b8181101561091a578581018301518582016040015282016108fe565b8181111561092b5783604083870101525b50601f01601f1916929092016040019392505050565b6000821982111561096057634e487b7160e01b81526011600452602481fd5b500190565b600181811c9082168061097957607f821691505b6020821081141561099a57634e487b7160e01b600052602260045260246000fd5b5091905056fea26469706673582212204090fc316510124c41761aa38bf0dc2852042412d8cf0472384c08d04f427a3c64736f6c63430008040033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100b45760003560e01c806370a082311161007157806370a082311461014157806395d89b411461016a578063a457c2d714610172578063a9059cbb14610185578063be29184f14610198578063dd62ed3e146101ad57600080fd5b806306fdde03146100b9578063095ea7b3146100d757806318160ddd146100fa57806323b872dd1461010c578063313ce5671461011f578063395093511461012e575b600080fd5b6100c16101c0565b6040516100ce91906108ee565b60405180910390f35b6100ea6100e53660046108c5565b610252565b60405190151581526020016100ce565b6002545b6040519081526020016100ce565b6100ea61011a366004610849565b61026a565b604051600681526020016100ce565b6100ea61013c3660046108c5565b61028e565b6100fe61014f3660046107f6565b6001600160a01b031660009081526020819052604090205490565b6100c16102b0565b6100ea6101803660046108c5565b6102bf565b6100ea6101933660046108c5565b61033f565b6101ab6101a6366004610884565b61034d565b005b6100fe6101bb366004610817565b610364565b6060600380546101cf90610965565b80601f01602080910402602001604051908101604052809291908181526020018280546101fb90610965565b80156102485780601f1061021d57610100808354040283529160200191610248565b820191906000526020600020905b81548152906001019060200180831161022b57829003601f168201915b5050505050905090565b60003361026081858561038f565b5060019392505050565b6000336102788582856104b3565b61028385858561052d565b506001949350505050565b6000336102608185856102a18383610364565b6102ab9190610941565b61038f565b6060600480546101cf90610965565b600033816102cd8286610364565b9050838110156103325760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b610283828686840361038f565b60003361026081858561052d565b61036082826001600160801b03166106fb565b5050565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103f15760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610329565b6001600160a01b0382166104525760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610329565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104bf8484610364565b90506000198114610527578181101561051a5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610329565b610527848484840361038f565b50505050565b6001600160a01b0383166105915760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610329565b6001600160a01b0382166105f35760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610329565b6001600160a01b0383166000908152602081905260409020548181101561066b5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610329565b6001600160a01b038085166000908152602081905260408082208585039055918516815290812080548492906106a2908490610941565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516106ee91815260200190565b60405180910390a3610527565b6001600160a01b0382166107515760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610329565b80600260008282546107639190610941565b90915550506001600160a01b03821660009081526020819052604081208054839290610790908490610941565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b80356001600160a01b03811681146107f157600080fd5b919050565b600060208284031215610807578081fd5b610810826107da565b9392505050565b60008060408385031215610829578081fd5b610832836107da565b9150610840602084016107da565b90509250929050565b60008060006060848603121561085d578081fd5b610866846107da565b9250610874602085016107da565b9150604084013590509250925092565b60008060408385031215610896578182fd5b61089f836107da565b915060208301356001600160801b03811681146108ba578182fd5b809150509250929050565b600080604083850312156108d7578182fd5b6108e0836107da565b946020939093013593505050565b6000602080835283518082850152825b8181101561091a578581018301518582016040015282016108fe565b8181111561092b5783604083870101525b50601f01601f1916929092016040019392505050565b6000821982111561096057634e487b7160e01b81526011600452602481fd5b500190565b600181811c9082168061097957607f821691505b6020821081141561099a57634e487b7160e01b600052602260045260246000fd5b5091905056fea26469706673582212204090fc316510124c41761aa38bf0dc2852042412d8cf0472384c08d04f427a3c64736f6c63430008040033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/src/bluefin_client_sui/contracts.py b/src/bluefin_client_sui/contracts.py new file mode 100644 index 0000000..b34c60e --- /dev/null +++ b/src/bluefin_client_sui/contracts.py @@ -0,0 +1,100 @@ +from .contract_abis import MarginBank, Perpetual, USDC + +class Contracts: + def __init__(self): + self.contracts = {} + self.contract_addresses = {} + + def set_contract_addresses(self,contract_address,market=None,name=None): + if market and name: + if name: + self.contract_addresses[market][name] = contract_address + else: + self.contract_addresses[market] = contract_address + elif name: + self.contract_addresses[name] = contract_address + else: + self.contract_addresses = contract_address + return + + def set_contracts(self,contract,name,market=None): + if market: + if market not in self.contracts: + self.contracts[market] = {} + if name: + self.contracts[market][name] = contract + else: + self.contracts[market] = contract + + elif name: + self.contracts[name] = contract + else: + self.contracts = contract + return + + + ## GETTERS + def get_contract_abi(self,name): + """ + Returns contract abi. + Inputs: + - name(str): The contract name. + """ + + if name == "MarginBank": + return MarginBank["abi"] + elif name == "Perpetual": + return Perpetual["abi"] + elif name == "USDC": + return USDC["abi"] + else: + raise Exception("Unknown contract name: {}".format(name)) + + def get_contract(self,name,market=""): + """ + Returns the contract object. + Inputs: + - name(str): The contract name. + - market(str): The market the contract belongs to (required for market specific contracts). + """ + try: + if name in self.contracts.keys(): + return self.contracts[name] + if market in self.contracts.keys() and name in self.contracts[market].keys(): + return self.contracts[market][name] + else: + return "Contract not found" + except Exception as e: + raise(Exception("Failed to get contract, Exception: {}".format(e))) + + def get_contract_address(self,name=None,market=None): + """ + Returns the contract address. If neither of the inputs provided, will return a dict with all contract addresses. + Inputs: + - name(str): The contract name. + - market(str): The market the contract belongs to (if only market provided will return all address of market as dict). + """ + try: + if market and name: + return self.contract_addresses[market][name] + elif market: + return self.contract_addresses[market] + elif name: + return self.contract_addresses["auxiliaryContractsAddresses"][name] + else: + return self.contract_addresses + except Exception as e: + raise(Exception("Failed to get contract address, Exception: {}".format(e))) + + + + def get_market_id(self,market: str)-> str: + """ + Returns the market id/Perpetual ID for the respective market. + Inputs: + - market(str) the name of the market for which you need perp id e.g ETH-PERP + """ + return self.contract_addresses[market.value]['Perpetual']['id'] + + + diff --git a/src/bluefin_client_sui/enumerations.py b/src/bluefin_client_sui/enumerations.py new file mode 100644 index 0000000..2430861 --- /dev/null +++ b/src/bluefin_client_sui/enumerations.py @@ -0,0 +1,105 @@ +from enum import Enum + +class ORDER_TYPE(Enum): + LIMIT = "LIMIT" + MARKET = "MARKET" + +class ORDER_SIDE(Enum): + BUY = "BUY" + SELL = "SELL" + +class MARKET_SYMBOLS(Enum): + BTC = "BTC-PERP" + ETH = "ETH-PERP" + SOL = "SOL-PERP" + LINK = "LINK-PERP" + MATIC = "MATIC-PERP" + DOGE = "DOGE-PERP" + ARB = "ARB-PERP" + + +class TIME_IN_FORCE(Enum): + FILL_OR_KILL = "FOK" + IMMEDIATE_OR_CANCEL = "IOC" + GOOD_TILL_TIME = "GTT" + +class ONBOARDING_MESSAGES(Enum): + ONBOARDING = "Firefly Onboarding" + KEY_DERIVATION = "Firefly Access Key" + +class ORDER_STATUS(Enum): + PENDING = "PENDING" + OPEN = "OPEN" + PARTIAL_FILLED = "PARTIAL_FILLED" + FILLED = "FILLED" + CANCELLING = "CANCELLING" + CANCELLED = "CANCELLED" + REJECTED = "REJECTED" + EXPIRED = "EXPIRED" + +class CANCEL_REASON(Enum): + UNDERCOLLATERALIZED = "UNDERCOLLATERALIZED" + INSUFFICIENT_BALANCE = "INSUFFICIENT_BALANCE" + USER_CANCELLED = "USER_CANCELLED" + EXCEEDS_MARKET_BOUND = "EXCEEDS_MARKET_BOUND" + COULD_NOT_FILL = "COULD_NOT_FILL" + EXPIRED = "EXPIRED" + USER_CANCELLED_ON_CHAIN = "USER_CANCELLED_ON_CHAIN" + SYSTEM_CANCELLED = "SYSTEM_CANCELLED" + SELF_TRADE = "SELF_TRADE" + POST_ONLY_FAIL = "POST_ONLY_FAIL" + FAILED = "FAILED" + NETWORK_DOWN = "NETWORK_DOWN" + +class Interval(Enum): + _1m = "1m" + _3m = "3m" + _5m = "5m" + _15m = "15m" + _30m = "30m" + _1h = "1h" + _2h = "2h" + _4h = "4h" + _6h = "6h" + _8h = "8h" + _12h = "12h" + _1d = "1d" + _3d = "3d" + _1w = "1w" + _1M = "1M" + +class SOCKET_EVENTS(Enum): + GET_LAST_KLINE_WITH_INTERVAL = "{symbol}@kline@{interval}" + # rooms that can be joined + GLOBAL_UPDATES_ROOM = "globalUpdates" + USER_UPDATES_ROOM = "userUpdates" + # events that can be listened + MARKET_DATA_UPDATE = "MarketDataUpdate" + RECENT_TRADES = "RecentTrades" + ORDERBOOK_UPDATE = "OrderbookUpdate" + ADJUST_MARGIN = "AdjustMargin" + MARKET_HEALTH = "MarketHealth" + EXCHANGE_HEALTH = "ExchangeHealth" + ORDER_UPDATE = "OrderUpdate" + ORDER_SENT_FOR_SETTLEMENT = "OrderSettlementUpdate" + ORDER_REQUEUE_UPDATE = "OrderRequeueUpdate" + ORDER_CANCELLATION = "OrderCancelled" + POSITION_UPDATE = "PositionUpdate" + USER_TRADE = "UserTrade" + USER_TRANSACTION = "UserTransaction" + ACCOUNT_DATA = "AccountDataUpdate" + + + +class MARGIN_TYPE(Enum): + ISOLATED = "ISOLATED" + CROSS = "CROSS" + + +class ADJUST_MARGIN(Enum): + ADD = "ADD" + REMOVE = "REMOVE" + +class TRADE_TYPE(Enum): + ISOLATED = "IsolatedTrader" + LIQUIDATION = "IsolatedLiquidation" \ No newline at end of file diff --git a/src/bluefin_client_sui/interfaces.py b/src/bluefin_client_sui/interfaces.py new file mode 100644 index 0000000..221f03c --- /dev/null +++ b/src/bluefin_client_sui/interfaces.py @@ -0,0 +1,223 @@ +from typing import TypedDict, List +from .enumerations import * + +class Order(TypedDict): + market: str + price: int + isBuy: bool + reduceOnly: bool + quantity: int + postOnly: bool + orderbookOnly: bool + leverage: int + expiration: int + salt: int + maker: str + ioc: bool + + +class SignedOrder(Order): + typedSignature: str + +class RequiredOrderFields(TypedDict): + #symbol: MARKET_SYMBOLS # market for which to create order + market: str + price: int # price at which to place order. Will be zero for a market order + quantity: int # quantity/size of order + side: ORDER_SIDE # BUY/SELL + orderType: ORDER_TYPE # MARKET/LIMIT + + +class OrderSignatureRequest(RequiredOrderFields): + leverage: int # (optional) leverage to take, default is 1 + reduceOnly: bool # (optional) is order to be reduce only true/false, default its false + salt: int # (optional) random number for uniqueness of order. Generated randomly if not provided + expiration: int # (optional) time at which order will expire. Will be set to 1 month if not provided + maker: str # (optional) maker of the order, if not provided the account used to initialize the client will be default maker + #isBuy: bool + postOnly: bool + orderBookOnly: bool + ioc: bool + +class OrderSignatureResponse(RequiredOrderFields): + maker: str + orderSignature: str + +class PlaceOrderRequest(OrderSignatureResponse): + timeInForce: TIME_IN_FORCE # FOK/IOC/GTT by default all orders are GTT + postOnly: bool # true/false, default is true + cancelOnRevert: bool # if true, the order will be cancelled in case of on-chain settlement error, default is false + clientId: str # id of the client + +class GetOrderbookRequest(TypedDict): + symbol: MARKET_SYMBOLS + limit: int # number of bids/asks to retrieve, should be <= 50 + +class OnboardingMessage(TypedDict): + action: str + onlySignOn: str + +class OrderResponse(TypedDict): + id: int + clientId: str + requestTime: int + cancelReason: CANCEL_REASON + orderStatus: ORDER_STATUS + hash: str + symbol: MARKET_SYMBOLS + orderType: ORDER_TYPE + timeInForce: TIME_IN_FORCE + userAddress: str + side: ORDER_SIDE + price: str + quantity: str + leverage: str + reduceOnly: bool + expiration: int + salt: int + orderSignature: str + filledQty: str + avgFillPrice: str + createdAt: int + updatedAt: int + makerFee: str + takerFee: str + openQty: str + cancelOnRevert: bool + + +class GetOrderResponse(OrderResponse): + fee: str + postOnly: bool + triggerPrice: str + + +class GetCandleStickRequest(TypedDict): + symbol: MARKET_SYMBOLS + interval: Interval + startTime: float + endTime: float + limit: int + +class GetMarketRecentTradesRequest(TypedDict): + symbol: MARKET_SYMBOLS + pageSize: int + pageNumber: int + traders: TRADE_TYPE + +class OrderCancelSignatureRequest(TypedDict): + symbol: MARKET_SYMBOLS + hashes: list + parentAddress: str # (optional) should only be provided by a sub account + +class OrderCancellationRequest(OrderCancelSignatureRequest): + signature: str + +class CancelOrder(TypedDict): + hash: str + reason: str + + +class CancelOrderResponse(TypedDict): + message: str + data: dict + + +class GetTransactionHistoryRequest(TypedDict): + symbol: MARKET_SYMBOLS # will fetch orders of provided market + pageSize: int # will get only provided number of orders must be <= 50 + pageNumber: int # will fetch particular page records. A single page contains 50 records. + +class GetPositionRequest(GetTransactionHistoryRequest): + parentAddress : str # (optional) should be provided by sub accounts + +class GetUserTradesRequest(TypedDict): + symbol: MARKET_SYMBOLS + maker: bool + fromId: int + startTime: int + endTime: int + pageSize: int + pageNumber: int + type: ORDER_TYPE + parentAddress: str # (optional) should be provided by sub account + +class GetOrderRequest(GetTransactionHistoryRequest): + statuses:List[ORDER_STATUS] # (optional) status of orders to be fetched + orderId: int #(optional) the id of order to be fetched + orderType: List[ORDER_TYPE] # (optional) type of order Limit/Market + orderHashes: List[str] # (optional) hashes of order to be fetched + parentAddress : str # (optional) should be provided by sub accounts + +class GetFundingHistoryRequest(TypedDict): + symbol: MARKET_SYMBOLS # will fetch orders of provided market + pageSize: int # will get only provided number of orders must be <= 50 + cursor: int # will fetch particular page records. A single page contains 50 records. + parentAddress: str # (optional) should be provided by a sub account + + +class FundingHistoryResponse(TypedDict): + id: int # unique id + symbol: MARKET_SYMBOLS # market for which to create order + userAddress: str # user public address + quantity: int # size of position + time: int # created time + appliedFundingRate: str # funding rate percent applied + isFundingRatePositive: bool # was funding rate +ve or -ve + payment: str # amount + isPaymentPositive: bool # whether payment was deducted or added + oraclePrice: str # price from oracle + side: ORDER_SIDE # BUY/SELL + blockNumber: int # transaction block number + isPositionPositive: bool # is position LONG or SHORT + +class GetFundingHistoryResponse(TypedDict): + isMoreDataAvailable: bool # boolean indicating if there is more data available + nextCursor: int # next page number + data: List[FundingHistoryResponse] + +class GetTransferHistoryRequest(TypedDict): + pageSize: int # will get only provided number of orders must be <= 50 + cursor: int # will fetch particular page records. A single page contains 50 records. + action: str # (optional) Deposit / Withdraw + +class UserTransferHistoryResponse(TypedDict): + id: int # unique id + status: str # status of transaction + action: str # Deposit / Withdraw + amount: str # amount withdrawn/deposited + userAddress: str # user public address + blockNumber: int # transaction block number + latestTxHash: str # transaction hash + time: int # created time + createdAt: int + updatedAt: int + +class GetUserTransferHistoryResponse(TypedDict): + isMoreDataAvailable: bool # boolean indicating if there is more data available + nextCursor: int # next page number + data: List[UserTransferHistoryResponse] + +class CountDown(TypedDict): + symbol: str + countDown: int + +class GetCancelOnDisconnectTimerRequest(TypedDict): + symbol: MARKET_SYMBOLS # will fetch Cancel On Disconnect Timer of provided market + parentAddress: str # (optional) should be provided by a sub account + +class PostTimerAttributes(TypedDict): + countDowns: List[CountDown] + parentAddress: str + +class FailedCountDownResetResponse(TypedDict): + symbol: str + reason: str + +class PostTimerResponse(TypedDict): + acceptedToReset: List[str] + failedReset: List[FailedCountDownResetResponse] + + + + diff --git a/src/bluefin_client_sui/onboarding_signer.py b/src/bluefin_client_sui/onboarding_signer.py new file mode 100644 index 0000000..308f5c5 --- /dev/null +++ b/src/bluefin_client_sui/onboarding_signer.py @@ -0,0 +1,29 @@ +from .interfaces import * +from .signer import Signer +import hashlib +import json + +class OnboardingSigner(Signer): + def __init__(self): + super().__init__() + + def create_signature(self, msg, private_key, encoding="utf-8"): + """ + Signs the message. + Inputs: + - msg: the message to be signed + - private_key: the signer's private key + Returns: + - str: signed msg hash + """ + msgDict={} + msgDict['onboardingUrl']=msg + msg=json.dumps(msgDict,separators=(',', ':')) + msg_bytearray=bytearray(msg.encode("utf-8")) + intent=bytearray() + intent.extend([3,0,0, len(msg_bytearray)]) + intent=intent+msg_bytearray + + hash=hashlib.blake2b(intent,digest_size=32) + return self.sign_hash(hash.digest(), private_key) + diff --git a/src/bluefin_client_sui/order_signer.py b/src/bluefin_client_sui/order_signer.py new file mode 100644 index 0000000..eb9e2ca --- /dev/null +++ b/src/bluefin_client_sui/order_signer.py @@ -0,0 +1,111 @@ +from .utilities import bn_to_bytes8, address_to_bytes32,numberToHex, hexToByteArray +from .constants import * +from .signer import Signer +from .interfaces import Order +import hashlib +import json + +class OrderSigner(Signer): + def __init__(self, network_id, orders_contract_address="", domain="IsolatedTrader", version="1.0"): + super().__init__() + self.network_id = network_id + self.contract_address = orders_contract_address + self.domain = domain + self.version = version + + def get_order_flags(self, order): + + ''' 0th bit = ioc + 1st bit = postOnly + 2nd bit = reduceOnly + 3rd bit = isBuy + 4th bit = orderbookOnly + e.g. 00000000 // all flags false + e.g. 00000001 // ioc order, sell side, can be executed by taker + e.e. 00010001 // same as above but can only be executed by settlement operator + ''' + flag = 0 + if order['ioc']: + flag+=1 + if order['postOnly']: + flag+=2 + if order['reduceOnly']: + flag+=4 + if order['isBuy']: + flag+=8 + if order['orderbookOnly']: + flag+=16 + return flag + + def get_domain_hash(self): + """ + Returns domain hash + """ + ### todo + return "" + + + def get_order_hash(self, order:Order, withBufferHex=True): + """ + Returns order hash. + Inputs: + - order: the order to be signed + Returns: + - str: order hash + """ + flags = self.get_order_flags(order) + flags = hexToByteArray(numberToHex(flags,2)) + + buffer=bytearray() + orderPriceHex=hexToByteArray(numberToHex(int(order["price"]))) + orderQuantityHex=hexToByteArray(numberToHex(int(order['quantity']))) + orderLeverageHex=hexToByteArray (numberToHex(int(order['leverage']))) + orderSalt=hexToByteArray(numberToHex(int(order['salt']))) + orderExpiration=hexToByteArray(numberToHex(int(order['expiration']),16)) + orderMaker=hexToByteArray(numberToHex(int(order['maker'],16),64)) + orderMarket=hexToByteArray(numberToHex(int(order['market'],16),64)) + bluefin=bytearray("Bluefin", encoding="utf-8") + + buffer=orderPriceHex+orderQuantityHex+orderLeverageHex+orderSalt+orderExpiration+orderMaker+orderMarket+flags+bluefin + + #for cancel order signature verification we use buffer directly + # for placing order we use buffer.hex().encode("utf-8") + if withBufferHex: + order_hash=hashlib.sha256(buffer.hex().encode("utf-8")).digest() + else: + order_hash=hashlib.sha256(buffer).digest() + return order_hash + def sign_order(self, order:Order, private_key): + """ + Used to create an order signature. The method will use the provided key + in params(if any) to sign the order. + + Args: + order (Order): an order containing order fields (look at Order interface) + private_key (str): private key of the account to be used for signing + + Returns: + str: generated signature + """ + order_hash = self.get_order_hash(order) + return self.sign_hash(order_hash, private_key, "") + + def sign_cancellation_hash(self,order_hash:list): + """ + Used to create a cancel order signature. The method will use the provided key + in params(if any) to sign the cancel order. + + Args: + order_hash(list): a list containing all orders to be cancelled + private_key (str): private key of the account to be used for signing + Returns: + str: generated signature + """ + sigDict={} + sigDict['orderHashes']=order_hash + encodedMessage=self.encode_message(sigDict) + return encodedMessage + + + + \ No newline at end of file diff --git a/src/bluefin_client_sui/signer.py b/src/bluefin_client_sui/signer.py new file mode 100644 index 0000000..80a72c4 --- /dev/null +++ b/src/bluefin_client_sui/signer.py @@ -0,0 +1,45 @@ +import nacl +import hashlib +import json +class Signer: + def __init__(self): + pass + + def sign_hash(self, hash, private_key, append=''): + """ + Signs the hash and returns the signature. + """ + result= nacl.signing.SigningKey(private_key).sign(hash)[:64] + return result.hex()+'1' + append + + + def encode_message(self,msg: dict): + msg=json.dumps(msg,separators=(',', ':')) + msg_bytearray=bytearray(msg.encode("utf-8")) + intent=bytearray() + encodeLengthBCS=self.decimal_to_bcs(len(msg_bytearray)) + intent.extend([3,0,0]) + intent.extend(encodeLengthBCS) + intent=intent+msg_bytearray + hash=hashlib.blake2b(intent,digest_size=32) + return hash.digest() + + def decimal_to_bcs(self,num): + # Initialize an empty list to store the BCS bytes + bcs_bytes = [] + while num > 0: + # Take the last 7 bits of the number + bcs_byte = num & 0x7F + + # Set the most significant bit (MSB) to 1 if there are more bytes to follow + if num > 0x7F: + bcs_byte |= 0x80 + + # Append the BCS byte to the list + bcs_bytes.append(bcs_byte) + + # Right-shift the number by 7 bits to process the next portion + num >>= 7 + + return bcs_bytes + \ No newline at end of file diff --git a/src/bluefin_client_sui/socket_manager.py b/src/bluefin_client_sui/socket_manager.py new file mode 100644 index 0000000..9a13fe7 --- /dev/null +++ b/src/bluefin_client_sui/socket_manager.py @@ -0,0 +1,103 @@ +import logging +import threading +from websocket import ( + ABNF, + create_connection, + WebSocketException, + WebSocketConnectionClosedException, +) + + +class SocketManager(threading.Thread): + def __init__( + self, + stream_url, + on_message=None, + on_open=None, + on_close=None, + on_error=None, + on_ping=None, + on_pong=None, + logger=None, + ): + threading.Thread.__init__(self) + if not logger: + logger = logging.getLogger(__name__) + self.logger = logger + self.stream_url = stream_url + self.on_message = on_message + self.on_open = on_open + self.on_close = on_close + self.on_ping = on_ping + self.on_pong = on_pong + self.on_error = on_error + + def create_ws_connection(self): + self.logger.debug( + "Creating connection with WebSocket Server: %s", self.stream_url + ) + self.ws = create_connection(self.stream_url) + self.logger.debug( + "WebSocket connection has been established: %s", self.stream_url + ) + self._callback(self.on_open) + + def run(self): + self.read_data() + + def send_message(self, message): + self.logger.debug("Sending message to WebSocket Server: %s", message) + self.ws.send(message) + + def ping(self): + self.ws.ping() + + def read_data(self): + data = "" + while True: + try: + op_code, frame = self.ws.recv_data_frame(True) + except WebSocketException as e: + if isinstance(e, WebSocketConnectionClosedException): + self.logger.error("Lost websocket connection") + else: + self.logger.error("Websocket exception: {}".format(e)) + raise e + except Exception as e: + self.logger.error("Exception in read_data: {}".format(e)) + raise e + + if op_code == ABNF.OPCODE_CLOSE: + self.logger.warning( + "CLOSE frame received, closing websocket connection" + ) + self._callback(self.on_close) + break + elif op_code == ABNF.OPCODE_PING: + self._callback(self.on_ping, frame.data) + self.ws.pong("") + self.logger.debug("Received Ping; PONG frame sent back") + elif op_code == ABNF.OPCODE_PONG: + self.logger.debug("Received PONG frame") + self._callback(self.on_pong) + else: + data = frame.data + if op_code == ABNF.OPCODE_TEXT: + data = data.decode("utf-8") + self._callback(self.on_message, data) + + def close(self): + if not self.ws.connected: + self.logger.warn("Websocket already closed") + else: + self.ws.send_close() + return + + def _callback(self, callback, *args): + if callback: + try: + callback(self, *args) + except Exception as e: + self.logger.error("Error from callback {}: {}".format(callback, e)) + if self.on_error: + self.on_error(self, e) \ No newline at end of file diff --git a/src/bluefin_client_sui/sockets_lib.py b/src/bluefin_client_sui/sockets_lib.py new file mode 100644 index 0000000..f883973 --- /dev/null +++ b/src/bluefin_client_sui/sockets_lib.py @@ -0,0 +1,194 @@ +import socketio +import time +from .enumerations import MARKET_SYMBOLS, SOCKET_EVENTS +import asyncio +sio = socketio.Client() + + +class Sockets: + callbacks = {} + + def __init__(self, url, timeout=10, token=None) -> None: + self.url = url + self.timeout = timeout + self.token = token + self.api_token = "" + return + + def _establish_connection(self): + """ + Connects to the desired url + """ + try: + sio.connect(self.url, wait_timeout=self.timeout, + transports=["websocket"]) + return True + except: + return False + + def set_token(self, token): + """ + Sets default user token + Inputs: + - token (user auth token): firefly onboarding token. + """ + self.token = token + + def set_api_token(self, token): + """ + Sets default user token + Inputs: + - token (user auth token): firefly onboarding token. + """ + self.api_token = token + + async def open(self): + """ + opens socket instance connection + """ + self.connection_established = self._establish_connection() + if not self.connection_established: + await self.close() + raise (Exception("Failed to connect to Host: {}".format(self.url))) + return + + async def close(self): + """ + closes the socket instance connection + """ + sio.disconnect() + return + + @sio.on("*") + def listener(event, data): + """ + Listens to all events emitted by the server + """ + try: + if event in Sockets.callbacks.keys(): + Sockets.callbacks[event](data) + elif "default" in Sockets.callbacks.keys(): + Sockets.callbacks["default"]({"event": event, "data": data}) + else: + pass + except: + pass + return + + @sio.event + def connect(): + print("Connected To Socket Server") + # add 10 seconds sleep to allow connection to be established before callbacks for connections are executed + if 'connect' in Sockets.callbacks: + # Execute the callback using asyncio.run() if available + time.sleep(10) + asyncio.run(Sockets.callbacks['connect']()) + + + @sio.event + def disconnect(): + print('Disconnected From Socket Server') + if 'disconnect' in Sockets.callbacks: + # Execute the callback using asyncio.run() if available + asyncio.run(Sockets.callbacks['disconnect']()) + + + async def listen(self, event, callback): + """ + Assigns callbacks to desired events + """ + Sockets.callbacks[event] = callback + return + + async def subscribe_global_updates_by_symbol(self, symbol: MARKET_SYMBOLS): + """ + Allows user to subscribe to global updates for the desired symbol. + Inputs: + - symbol: market symbol of market user wants global updates for. (e.g. DOT-PERP) + """ + try: + if not self.connection_established: + raise Exception( + "Socket connection is established, invoke socket.open()") + + resp = sio.call('SUBSCRIBE', [ + { + "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, + "p": symbol.value, + }, + ]) + return resp["success"] + except Exception as e: + print("Error: ", e) + return False + + async def unsubscribe_global_updates_by_symbol(self, symbol: MARKET_SYMBOLS): + """ + Allows user to unsubscribe to global updates for the desired symbol. + Inputs: + - symbol: market symbol of market user wants to remove global updates for. (e.g. DOT-PERP) + """ + try: + if not self.connection_established: + return False + + resp = sio.call('UNSUBSCRIBE', [ + { + "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, + "p": symbol.value, + }, + ]) + + return resp["success"] + except Exception as e: + print(e) + return False + + async def subscribe_user_update_by_token(self, parent_account: str = None, user_token: str = None) -> bool: + """ + Allows user to subscribe to their account updates. + Inputs: + - parent_account(str): address of parent account. Only whitelisted + sub-account can listen to its parent account position updates + - token(str): auth token generated when onboarding on firefly + """ + try: + if not self.connection_established: + return False + + resp = sio.call("SUBSCRIBE", [ + { + "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, + 'pa': parent_account, + "t": self.token if user_token == None else user_token, + "rt": self.api_token + }, + ]) + + return resp["success"] + except Exception as e: + print(e) + return False + + async def unsubscribe_user_update_by_token(self, parent_account: str = None, user_token: str = None): + """ + Allows user to unsubscribe to their account updates. + Inputs: + - parent_account(str): address of parent account. Only for sub-accounts + - token: auth token generated when onboarding on firefly + """ + try: + if not self.connection_established: + return False + + resp = sio.call("UNSUBSCRIBE", [ + { + "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, + 'pa': parent_account, + "t": self.token if user_token == None else user_token, + "rt": self.api_token + }, + ]) + return resp["success"] + except: + return False diff --git a/src/bluefin_client_sui/utilities.py b/src/bluefin_client_sui/utilities.py new file mode 100644 index 0000000..77abeeb --- /dev/null +++ b/src/bluefin_client_sui/utilities.py @@ -0,0 +1,120 @@ +from datetime import datetime +from random import randint +#from web3 import Web3 +import time +import bip_utils +import hashlib +from typing import Union +from .constants import SUI_BASE_NUM, DAPI_BASE_NUM + +def toDapiBase(number: Union[int, float])->int: + return int(number*DAPI_BASE_NUM) + +def fromDapiBase(number: Union[int, float], dtype=int)-> int: + return dtype(number/DAPI_BASE_NUM) + +def toSuiBase(number: Union[int,float]) -> int: + return int(number*SUI_BASE_NUM) + + +def numberToHex(num, pad=32): + #converting number to Hexadecimal format + hexNum=hex(num) + + #padding it with zero to make the size 32 bytes + padHex=hexNum[2:].zfill(pad) + return padHex + +def hexToByteArray(hexStr): + return bytearray.fromhex(hexStr) + + +def mnemonicToPrivateKey(seedPhrase: str)-> str: + bip39_seed = bip_utils.Bip39SeedGenerator(seedPhrase).Generate() + bip32_ctx = bip_utils.Bip32Slip10Ed25519.FromSeed(bip39_seed) + derivation_path="m/44'/784'/0'/0'/0'" + bip32_der_ctx = bip32_ctx.DerivePath(derivation_path) + private_key: str = bip32_der_ctx.PrivateKey().Raw() + return private_key + +def privateKeyToPublicKey(privateKey: str)-> str: + privateKeyBytes=bytes(privateKey) + bip32_ctx = bip_utils.Bip32Slip10Ed25519.FromPrivateKey(privateKeyBytes) + public_key: str = bip32_ctx.PublicKey().RawCompressed() + return public_key + +def getAddressFromPublicKey(publicKey: str)-> str: + address: str = "0x" + hashlib.blake2b(publicKey.ToBytes(), digest_size=32).digest().hex()[:] + return address + + + + + + + + +def strip_hex_prefix(input): + if input[0:2] == '0x': + return input[2:] + else: + return input + +def address_to_bytes32(addr): + return '0x000000000000000000000000' + strip_hex_prefix(addr) + +def bn_to_bytes8(value:int): + return str("0x"+"0"*16+hex(value)[2:]).encode('utf-8') + +def default_value(dict, key, default_value): + if key in dict: + return dict[key] + else: + return default_value + +def default_enum_value(dict, key, default_value): + if key in dict: + return dict[key].value + else: + return default_value.value + + +def current_unix_timestamp(): + return int(datetime.now().timestamp()) + +def random_number(max_range): + return current_unix_timestamp() + randint(0, max_range) + randint(0, max_range) + +def extract_query(value:dict): + query="" + for i,j in value.items(): + query+="&{}={}".format(i,j) + return query[1:] + +def extract_enums(params:dict,enums:list): + for i in enums: + if i in params.keys(): + if type(params[i]) == list: + params[i] = [x.value for x in params[i]] + else: + params[i] = params[i].value + return params + +def config_logging(logging, logging_level, log_file: str = None): + """Configures logging to provide a more detailed log format, which includes date time in UTC + Example: 2021-11-02 19:42:04.849 UTC : + Args: + logging: python logging + logging_level (int/str): For logging to include all messages with log levels >= logging_level. Ex: 10 or "DEBUG" + logging level should be based on https://docs.python.org/3/library/logging.html#logging-levels + Keyword Args: + log_file (str, optional): The filename to pass the logging to a file, instead of using console. Default filemode: "a" + """ + + logging.Formatter.converter = time.gmtime # date time in GMT/UTC + logging.basicConfig( + level=logging_level, + filename=log_file, + format="%(asctime)s.%(msecs)03d UTC %(levelname)s %(name)s: %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) \ No newline at end of file diff --git a/src/bluefin_client_sui/websocket_client.py b/src/bluefin_client_sui/websocket_client.py new file mode 100644 index 0000000..34c87af --- /dev/null +++ b/src/bluefin_client_sui/websocket_client.py @@ -0,0 +1,182 @@ +import json +import logging +from .socket_manager import SocketManager +from .enumerations import MARKET_SYMBOLS, SOCKET_EVENTS + + +class WebsocketClient: + def __init__( + self, + stream_url, + token=None, + api_token=None, + logger=None, + ): + if not logger: + logger = logging.getLogger(__name__) + self.logger = logger + self.token = token + self.api_token = api_token + self.stream_url = stream_url + self.callbacks = {} + + def initialize_socket( + self, + on_open, + on_close=None, + on_error=None, + on_ping=None, + on_pong=None, + logger=None, + ): + self.socket_manager = SocketManager( + self.stream_url, + on_message=self.listener, + on_open=on_open, + on_close=on_close, + on_error=on_error, + on_ping=on_ping, + on_pong=on_pong, + logger=logger, + ) + + # start the thread + self.socket_manager.create_ws_connection() + self.logger.debug("WebSocket Client started.") + self.socket_manager.start() + + def set_token(self, token): + """ + Sets default user token + Inputs: + - token (user auth token): firefly onboarding token. + """ + self.token = token + + def set_api_token(self, token): + """ + Sets default user token + Inputs: + - token (user auth token): firefly onboarding token. + """ + self.api_token = token + + def listen(self, event, callback): + """ + Assigns callbacks to desired events + """ + self.callbacks[event] = callback + return + + def send(self, message: dict): + self.socket_manager.send_message(json.dumps(message)) + + def subscribe_global_updates_by_symbol(self, symbol: MARKET_SYMBOLS): + """ + Allows user to subscribe to global updates for the desired symbol. + Inputs: + - symbol: market symbol of market user wants global updates for. (e.g. DOT-PERP) + """ + try: + if not self.socket_manager.ws.connected: + raise Exception( + "Socket connection is established, invoke socket.open()") + + self.socket_manager.send_message(json.dumps(['SUBSCRIBE', [ + { + "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, + "p": symbol.value, + }, + ]])) + return True + except Exception: + return False + + def unsubscribe_global_updates_by_symbol(self, symbol: MARKET_SYMBOLS): + """ + Allows user to unsubscribe to global updates for the desired symbol. + Inputs: + - symbol: market symbol of market user wants to remove global updates for. (e.g. DOT-PERP) + """ + try: + if not self.socket_manager.ws.connected: + return False + + self.socket_manager.send_message(json.dumps((['UNSUBSCRIBE', [ + { + "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, + "p": symbol.value, + }, + ]]))) + return True + except: + return False + + def subscribe_user_update_by_token(self, user_token: str = None): + """ + Allows user to subscribe to their account updates. + Inputs: + - token(str): auth token generated when onboarding on firefly + """ + try: + if not self.socket_manager.ws.connected: + return False + + self.socket_manager.send_message(json.dumps((["SUBSCRIBE", [ + { + "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, + "t": self.token if user_token == None else user_token, + "rt": self.api_token + }, + ]]))) + return True + except: + return False + + def unsubscribe_user_update_by_token(self, user_token: str = None): + """ + Allows user to unsubscribe to their account updates. + Inputs: + - token: auth token generated when onboarding on firefly + """ + try: + if not self.socket_manager.ws.connected: + return False + + self.socket_manager.send_message(json.dumps((["UNSUBSCRIBE", [ + { + "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, + "t": self.token if user_token == None else user_token, + "rt": self.api_token + }, + ]]))) + return True + except: + return False + + def ping(self): + self.logger.debug("Sending ping to WebSocket Server") + self.socket_manager.ping() + + def stop(self, id=None): + self.socket_manager.close() + # self.socket_manager.join() + + def listener(self, _, message): + """ + Listens to all events emitted by the server + """ + data = json.loads(message) + event_name = data["eventName"] + try: + if event_name in self.callbacks: + callback = self.callbacks[event_name] + callback(data["data"]) + elif "default" in self.callbacks.keys(): + self.callbacks["default"]( + {"event": event_name, "data": data["data"]}) + else: + pass + except: + pass + return diff --git a/src/check.py b/src/check.py new file mode 100644 index 0000000..cc76e9c --- /dev/null +++ b/src/check.py @@ -0,0 +1,31 @@ +from bluefin_exchange_client_sui import FireflyClient, Networks +from pprint import pprint +import asyncio +TEST_ACCT_KEY="!#12" +TEST_NETWORK="SUI_STAGING" + + +async def main(): + # initialize client + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + # Initializing client for the private key provided. The second argument api_token is optional + await client.init(True) + + print('Account Address:', client.get_public_address()) + + # # gets user account data on-chain + data = await client.get_user_account_data() + + await client.close_connections() + + pprint(data) + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file From f9af8711d89cb595c2e9d378ffa52d336c4dcdf0 Mon Sep 17 00:00:00 2001 From: Ibad Ur Rahman Date: Wed, 23 Aug 2023 12:48:32 +0500 Subject: [PATCH 04/12] more changes (#3) --- README.md | 7 ++- examples/4.placing_orders.py | 4 +- examples/7.cancelling_orders.py | 22 ++-------- examples/8.exchange_data.py | 1 - requirements.in | 76 +++++++++++++-------------------- 5 files changed, 42 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index ba51960..9a3758b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Python Client for the Firefly Exchange API and Smart Contracts for SUI. ### Install -The package can be installed from [PyPi](https://pypi.org/project/firefly-exchange-client/) using pip: +The package can be installed from [PyPi](https://pypi.org/project/bluefin-client-sui/) using pip: ``` pip install bluefin_client_sui @@ -29,6 +29,9 @@ pip install . The package currently supports python `>=3.8`. Find complete documentation on the library at https://docs.firefly.exchange/. + + + ### Getting Started When initializing the client, users must accept [terms and conditions](https://firefly.exchange/terms-of-use) and define network object containing the following values: @@ -43,7 +46,7 @@ When initializing the client, users must accept [terms and conditions](https://f } ``` -Users can import predefined networks from [constants](https://github.com/fireflyprotocol/firefly_exchange_client/blob/main/src/firefly_exchange_client/constants.py): +Users can import predefined networks from [constants](https://github.com/fireflyprotocol/bluefin-client-python-sui/blob/main/src/bluefin_client_sui/constants.py): ```python from bluefin_client_sui import Networks diff --git a/examples/4.placing_orders.py b/examples/4.placing_orders.py index 68ffb75..da48268 100644 --- a/examples/4.placing_orders.py +++ b/examples/4.placing_orders.py @@ -11,6 +11,8 @@ async def place_limit_order(client: FireflyClient): # default leverage of account is set to 3 on firefly user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) + print ("User Leverage", user_leverage) + # creates a LIMIT order to be signed signature_request = OrderSignatureRequest( @@ -19,7 +21,7 @@ async def place_limit_order(client: FireflyClient): quantity=toSuiBase(0.01), # quantity side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.LIMIT, - leverage=toSuiBase(1) + leverage=toSuiBase(user_leverage) ) # create signed order diff --git a/examples/7.cancelling_orders.py b/examples/7.cancelling_orders.py index 19affe9..e29ca6b 100644 --- a/examples/7.cancelling_orders.py +++ b/examples/7.cancelling_orders.py @@ -18,24 +18,9 @@ async def main(): # must add market before cancelling its orders client.add_market(MARKET_SYMBOLS.ETH) #client.create_order_to_sign() - #await client.adjust_leverage(MARKET_SYMBOLS.ETH, 1) - - order = { - #"market": "0x25a869797387e2eaa09c658c83dc0deaba99bb02c94447339c06fdbe8287347e", - "symbol":MARKET_SYMBOLS.ETH, - "price": toSuiBase(1000), - "quantity":toSuiBase(1), - "side":ORDER_SIDE.SELL, - "orderType":ORDER_TYPE.LIMIT, - "leverage":toSuiBase(1), - "expiration":int(time.time()+(30*24*60*60))*1000, # a random time in future - "reduceOnly":False, - "salt":111000000, - "postOnly":False, - "orderType": ORDER_TYPE.LIMIT, - "orderbookOnly": True - - } + await client.adjust_leverage(MARKET_SYMBOLS.ETH, 1) + + # creates a LIMIT order to be signed order = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol @@ -51,6 +36,7 @@ async def main(): signed_order = client.create_signed_order(order) resp = await client.post_signed_order(signed_order) + print ("sleeping for two seconds") # sign order for cancellation using order hash diff --git a/examples/8.exchange_data.py b/examples/8.exchange_data.py index 0a769cf..35b4d06 100644 --- a/examples/8.exchange_data.py +++ b/examples/8.exchange_data.py @@ -6,7 +6,6 @@ from pprint import pprint import asyncio -TEST_NETWORK="SUI_STAGING" async def main(): diff --git a/requirements.in b/requirements.in index acbdc0c..47dfbbe 100644 --- a/requirements.in +++ b/requirements.in @@ -1,49 +1,33 @@ -aiohttp==3.8.3 +aiohttp==3.8.5 aiosignal==1.3.1 -async-timeout==4.0.2 -attrs==22.1.0 -base58==2.1.1 -bidict==0.22.0 -bitarray==2.6.0 -certifi==2022.12.7 -charset-normalizer==2.1.1 -cytoolz==0.12.0 -eth-abi==2.2.0 -eth-account==0.5.9 -eth-hash==0.5.1 -eth-keyfile==0.5.1 -eth-keys==0.3.4 -eth-rlp==0.2.1 -eth-typing==2.3.0 -eth-utils==1.9.5 -ethereum-utils==0.5.0 -frozenlist==1.3.3 -hexbytes==0.3.0 +asn1crypto==1.5.1 +async-timeout==4.0.3 +attrs==23.1.0 +bidict==0.22.1 +bip-utils==2.7.1 +cbor2==5.4.6 +cffi==1.15.1 +charset-normalizer==3.2.0 +coincurve==17.0.0 +crcmod==1.7 +ecdsa==0.18.0 +ed25519-blake2b==1.4 +frozenlist==1.4.0 +gevent==23.7.0 +greenlet==2.0.2 idna==3.4 -importlib-resources==5.10.1 -ipfshttpclient==0.8.0a2 -jsonschema==4.17.3 -lru-dict==1.1.8 -multiaddr==0.0.9 -multidict==6.0.3 -netaddr==0.8.0 -parsimonious==0.8.1 -pkgutil_resolve_name==1.3.10 -protobuf==3.19.5 -pycryptodome==3.16.0 -pyrsistent==0.19.2 -pysha3==1.0.2 -python-engineio==4.3.4 -python-socketio==5.7.2 -pywin32==305 -requests==2.28.1 -rlp==2.0.1 +multidict==6.0.4 +netifaces==0.10.6 +py-sr25519-bindings==0.2.0 +pycparser==2.21 +pycryptodome==3.18.0 +PyNaCl==1.5.0 +python-engineio==4.6.0 +python-socketio==5.8.0 six==1.16.0 -toolz==0.12.0 -urllib3==1.26.13 -varint==1.0.2 -web3==5.31.3 -websockets==9.1 -yarl==1.8.2 -zipp==3.11.0 -websocket-client==1.5.1 \ No newline at end of file +socketio==0.2.1 +websocket==0.2.1 +websocket-client==1.6.2 +yarl==1.9.2 +zope.event==5.0 +zope.interface==6.0 From 7f8bb88291743987da81360a4f47f25f1257fdb5 Mon Sep 17 00:00:00 2001 From: Ibad Ur Rahman Date: Wed, 23 Aug 2023 14:42:29 +0500 Subject: [PATCH 05/12] Libchange (#4) * more changes * adding more detail * contract changes --- CHANGES.md | 23 +++++++++++++++ examples/contract_call.py | 49 ++++++++++++++++++++++++++++++++ src/bluefin_client_sui/signer.py | 28 +++++++++++++++++- 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 examples/contract_call.py diff --git a/CHANGES.md b/CHANGES.md index ac72d4b..86191d9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -50,3 +50,26 @@ 1. We sign the order and send it to bluefin, now imagine we do not have the hash of our order. 2. We resign our order and get the hash and then we follow the similar approach as above. Please have a look at `7.cancelling_orders.py` in our examples file. + + +## A detailed Guide on Onboarding: +1. Basically as explained earlier when sign the onboarding url and send it to bluefin, bluefin returns us the TOKEN. +2. The change in this repo is following. +3. ```python + # imagine msg="https://testnet.bluefin.io" + msgDict={} + msgDict['onboardingUrl']=msg + msg=json.dumps(msgDict,separators=(',', ':')) + # we first create a json something like this '{"onboardingURL":"https://testnet.bluefin.io"} + + # we then convert this json to a bytearray + msg_bytearray=bytearray(msg.encode("utf-8")) + intent=bytearray() + #we then append [3,0,0,length of our json object] to our intent bytearray + intent.extend([3,0,0, len(msg_bytearray)]) + intent=intent+msg_bytearray + + # we then take a blake2b hash of intent bytearray we created + hash=hashlib.blake2b(intent,digest_size=32) + #then we finally sign the hash + ``` diff --git a/examples/contract_call.py b/examples/contract_call.py new file mode 100644 index 0000000..3ad7ddf --- /dev/null +++ b/examples/contract_call.py @@ -0,0 +1,49 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") +import base64 +from bluefin_client_sui.utilities import * +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +import asyncio +from bluefin_client_sui import signer +from bluefin_client_sui import * + +async def main(): + + # initialize client + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + await client.init(True) + + + + # add market that you wish to trade on ETH/BTC are supported currently + client.add_market(MARKET_SYMBOLS.ETH) + txBytes="AAADAQGPzuAZV5krLKJWD1WUXOjk7Guz2vki2pBMZJ6CXkRf1XLGgQAAAAAAAQAgH/qFdX+Vzs7ShiLTDkGkUsiFFt9TRQyxkTgS3YKNWWgAEOgDAAAAAAAAAAAAAAAAAAABAF82iNaL7Cly31h0P767ErFoKbQb8bxQeNNRAJDvbPWOC21hcmdpbl9iYW5rEndpdGhkcmF3X2Zyb21fYmFuawADAQAAAQEAAQIAH/qFdX+Vzs7ShiLTDkGkUsiFFt9TRQyxkTgS3YKNWWgBWiybg/9fRf7zXUmsdBU3umIFeucEZNWeMGzlLttPf86tHg8AAAAAACA3qZfzxP1+yVILtVkJ6LdfZvkF7gK877AJco9Xook9Th/6hXV/lc7O0oYi0w5BpFLIhRbfU0UMsZE4Et2CjVlo6AMAAAAAAAAA4fUFAAAAAAA=" + dec_msg=base64.b64decode(txBytes) + mysigner=signer.Signer() + + seed="negative repeat fold noodle symptom spirit spend trophy merge ethics math erupt" + sui_wallet=SuiWallet(seed=seed) + + #private_key=mnemonicToPrivateKey(seed) + #privateKeyBytes=private_key.ToBytes() + #publicKey=privateKeyToPublicKey(private_key) + #publicKeyBytes=publicKey.ToBytes() + + + result=mysigner.sign_tx(dec_msg, sui_wallet) + print (result) + + await client.close_connections() + + + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/src/bluefin_client_sui/signer.py b/src/bluefin_client_sui/signer.py index 80a72c4..b4632f0 100644 --- a/src/bluefin_client_sui/signer.py +++ b/src/bluefin_client_sui/signer.py @@ -1,9 +1,35 @@ import nacl import hashlib import json +import base64 +from .utilities import * +from .account import * class Signer: def __init__(self): pass + + def sign_tx(self, tx_bytes, sui_wallet: SuiWallet): + """ + expects the msg in bytes + expects public key in bytes. + expects privateKey in bytes + Signs the msg and returns the signature. + Returns the value in b64 encoded format + """ + + intent=bytearray() + intent.extend([0,0,0]) + intent=intent+tx_bytes + hash=hashlib.blake2b(intent,digest_size=32).digest() + + + result= nacl.signing.SigningKey(sui_wallet.privateKeyBytes).sign(hash)[:64] + temp=bytearray() + temp.append(0) + temp.extend(result) + temp.extend(sui_wallet.publicKeyBytes[1:]) + res=base64.b64encode(temp) + return res def sign_hash(self, hash, private_key, append=''): """ @@ -11,7 +37,7 @@ def sign_hash(self, hash, private_key, append=''): """ result= nacl.signing.SigningKey(private_key).sign(hash)[:64] return result.hex()+'1' + append - + def encode_message(self,msg: dict): msg=json.dumps(msg,separators=(',', ':')) From 82e8c433c8eedd9109118563fe674b191c6e71ae Mon Sep 17 00:00:00 2001 From: Ibad Ur Rahman Date: Wed, 23 Aug 2023 21:07:35 +0500 Subject: [PATCH 06/12] Libchange (#5) * more changes * adding more detail * contract changes * change for contracts * adding more contract calls --- examples/1.initialization.py | 4 +- examples/20.contract_call.py | 46 ++++++++++ examples/config.py | 3 +- examples/contract_call.py | 5 +- src/bluefin_client_sui/client.py | 118 +++++++++++++------------ src/bluefin_client_sui/contracts.py | 54 +++--------- src/bluefin_client_sui/rpc.py | 129 ++++++++++++++++++++++++++++ src/bluefin_client_sui/signer.py | 12 +-- 8 files changed, 268 insertions(+), 103 deletions(-) create mode 100644 examples/20.contract_call.py create mode 100644 src/bluefin_client_sui/rpc.py diff --git a/examples/1.initialization.py b/examples/1.initialization.py index 5fc4004..b37edc0 100644 --- a/examples/1.initialization.py +++ b/examples/1.initialization.py @@ -1,9 +1,11 @@ -from config import TEST_ACCT_KEY, TEST_NETWORK import os import sys + print (os.getcwd()) sys.path.append(os.getcwd()+"/src/") +from config import TEST_ACCT_KEY, TEST_NETWORK + from bluefin_client_sui import FireflyClient, Networks from pprint import pprint import asyncio diff --git a/examples/20.contract_call.py b/examples/20.contract_call.py new file mode 100644 index 0000000..ddd9967 --- /dev/null +++ b/examples/20.contract_call.py @@ -0,0 +1,46 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") +import base64 +from bluefin_client_sui.utilities import * +from config import TEST_ACCT_KEY, TEST_NETWORK +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +import asyncio +from bluefin_client_sui import signer +from bluefin_client_sui import * + +async def main(): + + # initialize client + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + await client.init(True) + ### you need to have usdc coins to deposit it to margin bank. + ### the below functions just gets usdc coins that you have. + usdc_coins=client.get_usdc_coins() + + coin_obj_id=usdc_coins["data"][0]["coinObjectId"] + await client.deposit_margin_to_bank(1000, coin_obj_id) + + + await client.withdraw_margin_from_bank(100) + + + await client.withdraw_all_margin_from_bank() + + + + + + + await client.close_connections() + + + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/examples/config.py b/examples/config.py index 0973abe..b5452c2 100644 --- a/examples/config.py +++ b/examples/config.py @@ -1,3 +1,4 @@ -TEST_ACCT_KEY = "milk fit tape notable input seek circle define deny rally camera sorry" +TEST_ACCT_KEY="negative repeat fold noodle symptom spirit spend trophy merge ethics math erupt" +#TEST_ACCT_KEY = "milk fit tape notable input seek circle define deny rally camera sorry" TEST_SUB_ACCT_KEY = "7540d48032c731b3a17947b63a04763492d84aef854246d355a703adc9b54ce9" TEST_NETWORK = "SUI_STAGING" \ No newline at end of file diff --git a/examples/contract_call.py b/examples/contract_call.py index 3ad7ddf..dbdd689 100644 --- a/examples/contract_call.py +++ b/examples/contract_call.py @@ -18,6 +18,9 @@ async def main(): ) await client.init(True) + await client.withdraw_margin_from_bank(1000) + + # add market that you wish to trade on ETH/BTC are supported currently @@ -46,4 +49,4 @@ async def main(): if __name__ == "__main__": loop = asyncio.new_event_loop() loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop.close() diff --git a/src/bluefin_client_sui/client.py b/src/bluefin_client_sui/client.py index 88de0c1..b95dd09 100644 --- a/src/bluefin_client_sui/client.py +++ b/src/bluefin_client_sui/client.py @@ -10,9 +10,9 @@ from .enumerations import * from .websocket_client import WebsocketClient from .account import * +from .signer import Signer +from .rpc import * -#from eth_account import Account -#from eth_utils import to_wei, from_wei class FireflyClient: def __init__(self, are_terms_accepted, network, private_key=""): @@ -30,28 +30,18 @@ def __init__(self, are_terms_accepted, network, private_key=""): self.contracts = Contracts() self.order_signers = {} self.onboarding_signer = OnboardingSigner() + self.contract_signer=Signer() - async def init(self, user_onboarding=True, api_token=""): + async def init(self, user_onboarding=True, api_token="", symbol:MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): """ Initialize the client. Inputs: user_onboarding (bool, optional): If set to true onboards the user address to exchange and gets authToken. Defaults to True. api_token(string, optional): API token to initialize client in read-only mode """ - self.contracts.contract_addresses = await self.get_contract_addresses() - self.contracts.set_contract_addresses(self.contracts.contract_addresses) -# if "error" in self.contracts.contract_addresses: -# raise Exception("Error initializing client: {}".format(self.contracts.contract_addresses["error"])) - - # adding auxiliaryContracts to contracts class - # for i,j in self.contracts.get_contract_address(market="ETH-PERP").items(): - # self.add_contract(name=i,address=j) - - # contracts pertaining to markets - # for k, v in self.contracts.contract_addresses.items(): - # if 'PERP' in k: - # self.add_contract(name="Perpetual",address=v["Perpetual"], market=k) + self.contracts.contract_addresses = await self.get_contract_addresses(symbol) + self.contracts.set_contract_addresses(self.contracts.contract_addresses, market=symbol) if api_token: self.apis.api_token = api_token @@ -355,7 +345,7 @@ async def post_signed_order(self, params:PlaceOrderRequest): ) ## Contract calls - async def deposit_margin_to_bank(self, amount): + async def deposit_margin_to_bank(self, amount: int, coin_id: str)-> bool: """ Deposits given amount of USDC from user's account to margin bank @@ -365,33 +355,21 @@ async def deposit_margin_to_bank(self, amount): Returns: Boolean: true if amount is successfully deposited, false otherwise """ - - usdc_contract = self.contracts.get_contract(name="USDC") - mb_contract = self.contracts.get_contract(name="MarginBank") - - - # approve funds on usdc + package_id=self.contracts.get_package_id() + user_address=self.account.getUserAddress() + callArgs=[] + callArgs.append(self.contracts.get_bank_id()) + callArgs.append(self.account.getUserAddress()) + callArgs.append(str(amount)) + callArgs.append(coin_id) + txBytes=rpc_unsafe_moveCall("https://fullnode.testnet.sui.io:443", callArgs, "deposit_to_bank","margin_bank",user_address, package_id) + signature=self.contract_signer.sign_tx(txBytes, self.account) + res=rpc_sui_executeTransactionBlock("https://fullnode.testnet.sui.io:443", + txBytes, + signature) - construct_txn = usdc_contract.functions.approve( - mb_contract.address, - amount).buildTransaction({ - 'from': self.account.address, - 'nonce': self.w3.eth.getTransactionCount(self.account.address), - }) - - self._execute_tx(construct_txn) - - # deposit to margin bank - construct_txn = mb_contract.functions.depositToBank( - self.account.address, - amount).buildTransaction({ - 'from': self.account.address, - 'nonce': self.w3.eth.getTransactionCount(self.account.address), - }) - - self._execute_tx(construct_txn) - return True + return res async def withdraw_margin_from_bank(self, amount): """ @@ -404,19 +382,45 @@ async def withdraw_margin_from_bank(self, amount): Boolean: true if amount is successfully withdrawn, false otherwise """ - mb_contract = self.contracts.get_contract(name="MarginBank") - - # withdraw from margin bank - construct_txn = mb_contract.functions.withdrawFromBank( - self.account.address, - amount).buildTransaction({ - 'from': self.account.address, - 'nonce': self.w3.eth.getTransactionCount(self.account.address), - }) + bank_id=self.contracts.get_bank_id() + account_address=self.account.getUserAddress() - self._execute_tx(construct_txn) + callArgs=[bank_id, account_address, str(amount)] + txBytes=rpc_unsafe_moveCall("https://fullnode.testnet.sui.io:443", + callArgs, + "withdraw_from_bank", + "margin_bank", + self.account.getUserAddress(), + self.contracts.get_package_id() + ) + signature=self.contract_signer.sign_tx(txBytes, self.account) + res=rpc_sui_executeTransactionBlock("https://fullnode.testnet.sui.io:443", + txBytes, + signature) + + return res + + async def withdraw_all_margin_from_bank(self): + bank_id=self.contracts.get_bank_id() + account_address=self.account.getUserAddress() + perp_id=self.contracts.get_perpetual_id() + + + callArgs=[bank_id, account_address] + txBytes=rpc_unsafe_moveCall("https://fullnode.testnet.sui.io:443", + callArgs, + "withdraw_all_margin_from_bank", + "margin_bank", + self.account.getUserAddress(), + self.contracts.get_package_id() + ) + signature=self.contract_signer.sign_tx(txBytes, self.account) + res=rpc_sui_executeTransactionBlock("https://fullnode.testnet.sui.io:443", + txBytes, + signature) + + return res - return True async def adjust_leverage(self, symbol, leverage, parentAddress:str=""): """ @@ -527,6 +531,13 @@ async def get_native_chain_token_balance(self): return from_wei(self.w3.eth.get_balance(self.w3.toChecksumAddress(self.account.address)), "ether") except Exception as e: raise(Exception("Failed to get balance, Exception: {}".format(e))) + def get_usdc_coins(self): + callArgs=[] + callArgs.append(self.account.getUserAddress()) + callArgs.append(self.contracts.get_currency_type()) + result=rpc_get_usdc_coins("https://fullnode.testnet.sui.io:443",callArgs ) + return result + async def get_usdc_balance(self): """ @@ -544,6 +555,7 @@ async def get_margin_bank_balance(self): Returns user's Margin Bank balance. """ try: + contract = self.contracts.get_contract(name="MarginBank") return from_wei(contract.functions.getAccountBankBalance(self.account.address).call(),"ether") except Exception as e: diff --git a/src/bluefin_client_sui/contracts.py b/src/bluefin_client_sui/contracts.py index b34c60e..2e8d204 100644 --- a/src/bluefin_client_sui/contracts.py +++ b/src/bluefin_client_sui/contracts.py @@ -1,55 +1,25 @@ from .contract_abis import MarginBank, Perpetual, USDC - +from .enumerations import * class Contracts: def __init__(self): self.contracts = {} self.contract_addresses = {} def set_contract_addresses(self,contract_address,market=None,name=None): - if market and name: - if name: - self.contract_addresses[market][name] = contract_address - else: - self.contract_addresses[market] = contract_address - elif name: - self.contract_addresses[name] = contract_address - else: - self.contract_addresses = contract_address - return + self.contract_addresses["auxiliaryContractsAddresses"]=contract_address['auxiliaryContractsAddresses'] + self.contract_addresses[market]=contract_address[market.value] - def set_contracts(self,contract,name,market=None): - if market: - if market not in self.contracts: - self.contracts[market] = {} - if name: - self.contracts[market][name] = contract - else: - self.contracts[market] = contract - elif name: - self.contracts[name] = contract - else: - self.contracts = contract - return + def get_perpetual_id(self, market: MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): + return self.contract_addresses[market]['Perpetual']['id'] + def get_package_id(self, market: MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): + return self.contract_addresses[market]['package']['id'] - - ## GETTERS - def get_contract_abi(self,name): - """ - Returns contract abi. - Inputs: - - name(str): The contract name. - """ - - if name == "MarginBank": - return MarginBank["abi"] - elif name == "Perpetual": - return Perpetual["abi"] - elif name == "USDC": - return USDC["abi"] - else: - raise Exception("Unknown contract name: {}".format(name)) - + def get_currency_type(self): + return self.contract_addresses['auxiliaryContractsAddresses']['objects']['Currency']['dataType'] + + def get_bank_id(self, market: MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): + return self.contract_addresses['auxiliaryContractsAddresses']['objects']['Bank']['id'] def get_contract(self,name,market=""): """ Returns the contract object. diff --git a/src/bluefin_client_sui/rpc.py b/src/bluefin_client_sui/rpc.py new file mode 100644 index 0000000..2afb631 --- /dev/null +++ b/src/bluefin_client_sui/rpc.py @@ -0,0 +1,129 @@ +import requests +import json + +def rpc_unsafe_moveCall(url,params, function_name: str, function_library: str, userAddress ,packageId, gasBudget=100000000 ): + base_dict={} + base_dict["jsonrpc"]="2.0" + base_dict["id"]=1689764924887 + base_dict["method"]="unsafe_moveCall" + base_dict["params"]=[] + base_dict["params"].extend([userAddress, packageId, function_library,function_name]) + base_dict["params"].append([]) + base_dict["params"].append(params) + + base_dict["params"].append(None) + base_dict["params"].append(str(gasBudget)) + + payload=json.dumps(base_dict) + + headers = {'Content-Type': 'application/json' } + response = requests.request("POST", url, headers=headers, data=payload) + result=json.loads(response.text) + return result['result']['txBytes'] + +def rpc_sui_executeTransactionBlock(url, txBytes, signature): + base_dict={} + base_dict["jsonrpc"]="2.0" + base_dict["id"]=5 + base_dict["method"]="sui_executeTransactionBlock" + base_dict["params"]=[] + base_dict["params"].append(txBytes) + base_dict["params"].append([signature]) + + outputTypeDict={ + "showInput": True, + "showEffects": True, + "showEvents": True, + "showObjectChanges": True + } + base_dict["params"].append( outputTypeDict) + base_dict["params"].append("WaitForLocalExecution") + payload=json.dumps(base_dict) + + headers = {'Content-Type': 'application/json' } + response = requests.request("POST", url, headers=headers, data=payload) + result=json.loads(response.text) + return result + + +def rpc_get_usdc_coins(url, params, method="suix_getCoins"): + base_dict={} + base_dict["jsonrpc"]="2.0" + base_dict["id"]= 1 + base_dict["method"]=method + base_dict["params"]=params + payload=json.dumps(base_dict) + + headers = {'Content-Type': 'application/json' } + response = requests.request("POST", url, headers=headers, data=payload) + result=json.loads(response.text) + return result["result"] + +""" + +payload = json.dumps({ + "jsonrpc": "2.0", + "id": 5, + "method": "sui_executeTransactionBlock", + "params": [ + "AAADAQGPzuAZV5krLKJWD1WUXOjk7Guz2vki2pBMZJ6CXkRf1XLGgQAAAAAAAQAgH/qFdX+Vzs7ShiLTDkGkUsiFFt9TRQyxkTgS3YKNWWgAEOgDAAAAAAAAAAAAAAAAAAABAF82iNaL7Cly31h0P767ErFoKbQb8bxQeNNRAJDvbPWOC21hcmdpbl9iYW5rEndpdGhkcmF3X2Zyb21fYmFuawADAQAAAQEAAQIAH/qFdX+Vzs7ShiLTDkGkUsiFFt9TRQyxkTgS3YKNWWgBWiybg/9fRf7zXUmsdBU3umIFeucEZNWeMGzlLttPf86tHg8AAAAAACA3qZfzxP1+yVILtVkJ6LdfZvkF7gK877AJco9Xook9Th/6hXV/lc7O0oYi0w5BpFLIhRbfU0UMsZE4Et2CjVlo6AMAAAAAAAAA4fUFAAAAAAA=", + [ + "ANyIBWjL6U9T6qBoWWTc18qzVViytirDmwX+dOEqd77dibe0tgLcziDZpe3XoTRVbBJGUV9TIHCN2C21aNvUTA/JFlIyQTT87zRFBPBubLG+G22kP5UDgk3kIg8JPeUiBw==" + ], + { + "showInput": True, + "showEffects": True, + "showEvents": True, + "showObjectChanges": True + }, + "WaitForLocalExecution" + ] +}) +headers = { + 'Content-Type': 'application/json' +} + +response = requests.request("POST", url, headers=headers, data=payload) + +print(response.text) + + +""" + + + + + + + + +""" +url = "https://fullnode.testnet.sui.io:443" + +payload = json.dumps({ + "jsonrpc": "2.0", + "id": 1689764924887, + "method": "unsafe_moveCall", + "params": [ + "0x1ffa85757f95ceced28622d30e41a452c88516df53450cb1913812dd828d5968", + "0x5f3688d68bec2972df58743fbebb12b16829b41bf1bc5078d3510090ef6cf58e", + "margin_bank", + "withdraw_from_bank", + [], + [ + "0x8fcee01957992b2ca2560f55945ce8e4ec6bb3daf922da904c649e825e445fd5", + "0x1ffa85757f95ceced28622d30e41a452c88516df53450cb1913812dd828d5968", + "1000" + ], + "0x5a2c9b83ff5f45fef35d49ac741537ba62057ae70464d59e306ce52edb4f7fce", + "100000000" + ] +}) +headers = { + 'Content-Type': 'application/json' +} + +response = requests.request("POST", url, headers=headers, data=payload) + +print(response.text) +""" \ No newline at end of file diff --git a/src/bluefin_client_sui/signer.py b/src/bluefin_client_sui/signer.py index b4632f0..dc599b3 100644 --- a/src/bluefin_client_sui/signer.py +++ b/src/bluefin_client_sui/signer.py @@ -8,14 +8,15 @@ class Signer: def __init__(self): pass - def sign_tx(self, tx_bytes, sui_wallet: SuiWallet): + + def sign_tx(self, tx_bytes_str: str, sui_wallet: SuiWallet) -> str: """ - expects the msg in bytes - expects public key in bytes. - expects privateKey in bytes + expects the msg in str + expects the suiwallet object Signs the msg and returns the signature. Returns the value in b64 encoded format """ + tx_bytes=base64.b64decode(tx_bytes_str) intent=bytearray() intent.extend([0,0,0]) @@ -29,7 +30,8 @@ def sign_tx(self, tx_bytes, sui_wallet: SuiWallet): temp.extend(result) temp.extend(sui_wallet.publicKeyBytes[1:]) res=base64.b64encode(temp) - return res + return res.decode() + def sign_hash(self, hash, private_key, append=''): """ From 7a231923503c6f1e737a555da8c681b323b333d8 Mon Sep 17 00:00:00 2001 From: Ibad Ur Rahman Date: Fri, 25 Aug 2023 18:01:07 +0500 Subject: [PATCH 07/12] contract changes (#6) * adding sub account * adding adjusting leverage --- examples/11.sub_accounts.py | 12 +- examples/20.contract_call.py | 18 ++- examples/4.placing_orders.py | 13 +- examples/5.adjusting_leverage.py | 13 +- examples/6.adjusting_margin.py | 79 +++++++++++- src/bluefin_client_sui/client.py | 184 ++++++++++++++++++---------- src/bluefin_client_sui/constants.py | 2 +- src/bluefin_client_sui/contracts.py | 10 +- src/bluefin_client_sui/rpc.py | 2 +- src/bluefin_client_sui/utilities.py | 4 + 10 files changed, 243 insertions(+), 94 deletions(-) diff --git a/examples/11.sub_accounts.py b/examples/11.sub_accounts.py index 651bc72..d0a8e3a 100644 --- a/examples/11.sub_accounts.py +++ b/examples/11.sub_accounts.py @@ -1,6 +1,9 @@ +import sys,os +sys.path.append(os.getcwd()+"/src/") from config import TEST_ACCT_KEY, TEST_SUB_ACCT_KEY, TEST_NETWORK from bluefin_client_sui import FireflyClient, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, Networks, OrderSignatureRequest import asyncio +from bluefin_client_sui.utilities import * async def main(): @@ -16,22 +19,23 @@ async def main(): print("Child: ", clientChild.get_public_address()) # # whitelist sub account - status = await clientParent.update_sub_account(MARKET_SYMBOLS.ETH, clientChild.get_public_address(), True) + status = await clientParent.update_sub_account(clientChild.get_public_address(), True) print("Sub account created: {}".format(status)) clientChild.add_market(MARKET_SYMBOLS.ETH) parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) - + await clientParent.adjust_leverage(MARKET_SYMBOLS.ETH,1) + parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # sub account is only whitelisted for ETH market maker=clientParent.get_public_address(), # maker of the order is the parent account price=0, - quantity=0.02, + quantity=toSuiBase(0.02), side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.MARKET, - leverage=parent_leverage, + leverage=toSuiBase(parent_leverage), ) # order is signed using sub account's private key diff --git a/examples/20.contract_call.py b/examples/20.contract_call.py index ddd9967..9277d37 100644 --- a/examples/20.contract_call.py +++ b/examples/20.contract_call.py @@ -21,15 +21,27 @@ async def main(): ### the below functions just gets usdc coins that you have. usdc_coins=client.get_usdc_coins() - coin_obj_id=usdc_coins["data"][0]["coinObjectId"] + coin_obj_id=usdc_coins["data"][1]["coinObjectId"] await client.deposit_margin_to_bank(1000, coin_obj_id) - await client.withdraw_margin_from_bank(100) + #await client.withdraw_margin_from_bank(100) - await client.withdraw_all_margin_from_bank() + #await client.withdraw_all_margin_from_bank() + print ("Printing Margin Bank balance") + print (await client.get_margin_bank_balance()) + + print ("Printing usdc balance") + print (await client.get_usdc_balance()) + + print ("Printing SUI balance") + print (await client.get_native_chain_token_balance()) + + + print ("getting usdc coins") + print (client.get_usdc_coins()) diff --git a/examples/4.placing_orders.py b/examples/4.placing_orders.py index da48268..6ca074e 100644 --- a/examples/4.placing_orders.py +++ b/examples/4.placing_orders.py @@ -42,19 +42,8 @@ async def place_market_order(client: FireflyClient): # default leverage of account is set to 3 on firefly user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) - # creates a LIMIT order to be signed - ''' - signature_request = OrderSignatureRequest( - symbol=MARKET_SYMBOLS.ETH, # market symbol - price=0, # price at which you want to place order - quantity=0.01, # quantity - side=ORDER_SIDE.BUY, - orderType=ORDER_TYPE.MARKET, - leverage=user_leverage - ) ''' signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, - # market = "0x25a869797387e2eaa09c658c83dc0deaba99bb02c94447339c06fdbe8287347e", price = toSuiBase(0), quantity = toSuiBase(1), leverage = toSuiBase(1), @@ -96,7 +85,7 @@ async def main(): client.add_market(MARKET_SYMBOLS.ETH) # await place_limit_order(client) - await place_market_order(client) + await (client) await place_limit_order(client) await client.close_connections() diff --git a/examples/5.adjusting_leverage.py b/examples/5.adjusting_leverage.py index 0734be3..52d185a 100644 --- a/examples/5.adjusting_leverage.py +++ b/examples/5.adjusting_leverage.py @@ -12,10 +12,11 @@ async def main(): client = FireflyClient( True, # agree to terms and conditions Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY, # private key of wallet + TEST_ACCT_KEY # private key of wallet + ) - await client.init(True) + await client.init(True, symbol= MARKET_SYMBOLS.BTC) print('Leverage on BTC market:', await client.get_user_leverage(MARKET_SYMBOLS.BTC)) # we have a position on BTC so this will perform on-chain leverage update @@ -24,6 +25,14 @@ async def main(): print('Leverage on BTC market:', await client.get_user_leverage(MARKET_SYMBOLS.BTC)) + # initialize client + client = FireflyClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + await client.init(True, symbol=MARKET_SYMBOLS.ETH) print('Leverage on ETH market:', await client.get_user_leverage(MARKET_SYMBOLS.ETH)) # since we don't have a position on-chain, it will perform off-chain leverage adjustment diff --git a/examples/6.adjusting_margin.py b/examples/6.adjusting_margin.py index a2f6db8..096c554 100644 --- a/examples/6.adjusting_margin.py +++ b/examples/6.adjusting_margin.py @@ -3,17 +3,86 @@ from config import TEST_ACCT_KEY, TEST_NETWORK from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ADJUST_MARGIN -from eth_utils import from_wei import asyncio - +from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +from bluefin_client_sui.utilities import toSuiBase TEST_NETWORK="SUI_STAGING" + +async def place_limit_order(client: FireflyClient): + + # default leverage of account is set to 3 on firefly + user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) + await client.adjust_leverage(MARKET_SYMBOLS.ETH, 3) + user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) + + print ("User Leverage", user_leverage) + + + # creates a LIMIT order to be signed + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # market symbol + price=toSuiBase(1636.8), # price at which you want to place order + quantity=toSuiBase(0.01), # quantity + side=ORDER_SIDE.BUY, + orderType=ORDER_TYPE.LIMIT, + leverage=toSuiBase(user_leverage) + ) + + # create signed order + signed_order = client.create_signed_order(signature_request) + + print("Placing a limit order") + # place signed order on orderbook + resp = await client.post_signed_order(signed_order) + + # returned order with PENDING state + print(resp) + + return + +async def place_market_order(client: FireflyClient): + + + # default leverage of account is set to 3 on firefly + user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) + + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, + price = toSuiBase(0), + quantity = toSuiBase(1), + leverage = toSuiBase(user_leverage), + side = ORDER_SIDE.BUY, + orderType=ORDER_TYPE.MARKET, + ) + + # create signed order + signed_order = client.create_signed_order(signature_request) + + print("Placing a market order") + # place signed order on orderbook + resp = await client.post_signed_order(signed_order) + + # returned order with PENDING state + print(resp) + + + return async def main(): client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) + client.add_market(MARKET_SYMBOLS.ETH) + print (await client.get_usdc_balance()) + + #usdc_coins=client.get_usdc_coins() + #coin_obj_id=usdc_coins["data"][1]["coinObjectId"] + #await client.deposit_margin_to_bank(1000000000000, coin_obj_id) + + print (await client.get_margin_bank_balance()) + await place_market_order(client) position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) - print("Current margin in position:", from_wei(int(position["margin"]), "ether")) + print("Current margin in position:", position) # adding 100$ from our margin bank into our BTC position on-chain # must have native chain tokens to pay for gas fee @@ -23,14 +92,14 @@ async def main(): # to on-chain positions on exchange as off-chain infrastructure waits for blockchain # to emit position update event position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) - print("Current margin in position:", from_wei(int(position["margin"]), "ether")) + print("Current margin in position:",position["margin"]) # removing 100$ from margin await client.adjust_margin(MARKET_SYMBOLS.ETH, ADJUST_MARGIN.REMOVE, 100) position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) - print("Current margin in position:", from_wei(int(position["margin"]), "ether")) + print("Current margin in position:", int(position["margin"])) try: diff --git a/src/bluefin_client_sui/client.py b/src/bluefin_client_sui/client.py index b95dd09..4dcc3b5 100644 --- a/src/bluefin_client_sui/client.py +++ b/src/bluefin_client_sui/client.py @@ -31,6 +31,7 @@ def __init__(self, are_terms_accepted, network, private_key=""): self.order_signers = {} self.onboarding_signer = OnboardingSigner() self.contract_signer=Signer() + self.url=self.network['url'] async def init(self, user_onboarding=True, api_token="", symbol:MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): @@ -345,7 +346,7 @@ async def post_signed_order(self, params:PlaceOrderRequest): ) ## Contract calls - async def deposit_margin_to_bank(self, amount: int, coin_id: str)-> bool: + async def deposit_margin_to_bank(self, amount: int, coin_id: str, market=MARKET_SYMBOLS.ETH)-> bool: """ Deposits given amount of USDC from user's account to margin bank @@ -355,23 +356,28 @@ async def deposit_margin_to_bank(self, amount: int, coin_id: str)-> bool: Returns: Boolean: true if amount is successfully deposited, false otherwise """ - package_id=self.contracts.get_package_id() + package_id=self.contracts.get_package_id(market=market) user_address=self.account.getUserAddress() callArgs=[] callArgs.append(self.contracts.get_bank_id()) callArgs.append(self.account.getUserAddress()) callArgs.append(str(amount)) callArgs.append(coin_id) - txBytes=rpc_unsafe_moveCall("https://fullnode.testnet.sui.io:443", callArgs, "deposit_to_bank","margin_bank",user_address, package_id) + txBytes=rpc_unsafe_moveCall(self.url, + callArgs, + "deposit_to_bank", + "margin_bank", + user_address, + package_id) signature=self.contract_signer.sign_tx(txBytes, self.account) - res=rpc_sui_executeTransactionBlock("https://fullnode.testnet.sui.io:443", + res=rpc_sui_executeTransactionBlock(self.url, txBytes, signature) return res - async def withdraw_margin_from_bank(self, amount): + async def withdraw_margin_from_bank(self, amount, market=MARKET_SYMBOLS.ETH): """ Withdraws given amount of usdc from margin bank if possible @@ -386,36 +392,36 @@ async def withdraw_margin_from_bank(self, amount): account_address=self.account.getUserAddress() callArgs=[bank_id, account_address, str(amount)] - txBytes=rpc_unsafe_moveCall("https://fullnode.testnet.sui.io:443", + txBytes=rpc_unsafe_moveCall(self.url, callArgs, "withdraw_from_bank", "margin_bank", self.account.getUserAddress(), - self.contracts.get_package_id() + self.contracts.get_package_id(market=market) ) signature=self.contract_signer.sign_tx(txBytes, self.account) - res=rpc_sui_executeTransactionBlock("https://fullnode.testnet.sui.io:443", + res=rpc_sui_executeTransactionBlock(self.url, txBytes, signature) return res - async def withdraw_all_margin_from_bank(self): + async def withdraw_all_margin_from_bank(self, market=MARKET_SYMBOLS.ETH): bank_id=self.contracts.get_bank_id() account_address=self.account.getUserAddress() perp_id=self.contracts.get_perpetual_id() callArgs=[bank_id, account_address] - txBytes=rpc_unsafe_moveCall("https://fullnode.testnet.sui.io:443", + txBytes=rpc_unsafe_moveCall(self.url, callArgs, "withdraw_all_margin_from_bank", "margin_bank", self.account.getUserAddress(), - self.contracts.get_package_id() + self.contracts.get_package_id(market=market) ) signature=self.contract_signer.sign_tx(txBytes, self.account) - res=rpc_sui_executeTransactionBlock("https://fullnode.testnet.sui.io:443", + res=rpc_sui_executeTransactionBlock(self.url, txBytes, signature) @@ -441,14 +447,22 @@ async def adjust_leverage(self, symbol, leverage, parentAddress:str=""): account_address = self.account.address if parentAddress=="" else parentAddress # implies user has an open position on-chain, perform on-chain leverage update if(user_position != {}): - perp_contract = self.contracts.get_contract(name="Perpetual", market=symbol.value) - construct_txn = perp_contract.functions.adjustLeverage( - account_address, - toDapiBase(leverage)).buildTransaction({ - 'from': self.account.address, - 'nonce': self.w3.eth.getTransactionCount(self.account.address), - }) - self._execute_tx(construct_txn) + callArgs = []; + callArgs.append(self.contracts.get_perpetual_id(symbol)) + callArgs.append(self.contracts.get_bank_id()) + callArgs.append(self.contracts.get_sub_account_id()) + callArgs.append(account_address) + callArgs.append(str(toSuiBase(leverage))) + callArgs.append(self.contracts.get_price_oracle_object_id(symbol.value)) + txBytes=rpc_unsafe_moveCall(self.url, + callArgs, + "adjust_leverage", + "exchange", + self.account.getUserAddress(), + self.contracts.get_package_id(market=symbol)) + signature=self.contract_signer.sign_tx(txBytes, self.account) + result=rpc_sui_executeTransactionBlock(self.url, txBytes, signature) + return result else: await self.apis.post( @@ -464,7 +478,7 @@ async def adjust_leverage(self, symbol, leverage, parentAddress:str=""): return True - async def adjust_margin(self, symbol, operation, amount, parentAddress:str=""): + async def adjust_margin(self, symbol: MARKET_SYMBOLS, operation: ADJUST_MARGIN, amount: str, parentAddress:str=""): """ Adjusts user's on-chain position by adding or removing the specified amount of margin. Performs on-chain contract call, the user must have gas tokens @@ -480,84 +494,124 @@ async def adjust_margin(self, symbol, operation, amount, parentAddress:str=""): user_position = await self.get_user_position({"symbol":symbol, "parentAddress": parentAddress}) - account_address = Web3.toChecksumAddress(self.account.address if parentAddress == "" else parentAddress) - + if(user_position == {}): raise(Exception("User has no open position on market: {}".format(symbol))) else: - perp_contract = self.contracts.get_contract(name="Perpetual", market=symbol.value) - on_chain_call = perp_contract.functions.addMargin if operation == ADJUST_MARGIN.ADD else perp_contract.functions.removeMargin - - construct_txn = on_chain_call( - account_address, - to_wei(amount, "ether")).buildTransaction({ - 'from': self.account.address, - 'nonce': self.w3.eth.getTransactionCount(self.account.address), - }) - - self._execute_tx(construct_txn) - + callArgs = [] + callArgs.append(self.contracts.get_perpetual_id()) + callArgs.append(self.contracts.get_bank_id()) + + callArgs.append(self.contracts.get_sub_account_id()) + callArgs.append(self.account.getUserAddress()) + callArgs.append(str(amount)) + callArgs.append(self.contracts.get_price_oracle_object_id(symbol)) + if operation==ADJUST_MARGIN.ADD: + txBytes=rpc_unsafe_moveCall(self.url, + callArgs, + "add_margin", + "exchange", + self.account.getUserAddress(), + self.contracts.get_package_id()) + + else: + txBytes=rpc_unsafe_moveCall(self.url, + callArgs, + "remove_margin", + "exchange", + self.account.getUserAddress(), + self.contracts.get_package_id()) + + signature=self.contract_signer.sign_tx(txBytes, self.account) + result=rpc_sui_executeTransactionBlock(self.url,txBytes, signature) + return True - async def update_sub_account(self, symbol, sub_account_address, status): + + async def update_sub_account(self, sub_account_address: str, status: bool) -> bool: """ Used to whitelist and account as a sub account or revoke sub account status from an account. Inputs: - symbol (MARKET_SYMBOLS): market on which sub account status is to be updated sub_account_address (str): address of the sub account status (bool): new status of the sub account Returns: Boolean: true if the sub account status is update """ - perp_contract = self.contracts.get_contract(name="Perpetual", market=symbol.value) - - construct_txn = perp_contract.functions.setSubAccount( - sub_account_address, - status).buildTransaction({ - 'from': self.account.address, - 'nonce': self.w3.eth.getTransactionCount(self.account.address), - }) - - self._execute_tx(construct_txn) - - return True + callArgs=[] + callArgs.append(self.contracts.get_sub_account_id()) + callArgs.append(sub_account_address) + callArgs.append(status) + txBytes=rpc_unsafe_moveCall(self.url, + callArgs, + "set_sub_account", + "roles", + self.account.getUserAddress(), + self.contracts.get_package_id()) + + signature=self.contract_signer.sign_tx(txBytes, self.account) + + result=rpc_sui_executeTransactionBlock(self.url, txBytes, signature) + if result['result']['effects']['status']['status']=='success': + return True + else: + return False - async def get_native_chain_token_balance(self): + async def get_native_chain_token_balance(self)-> float: """ - Returns user's native chain token (ETH/BOBA) balance + Returns user's native chain token (SUI) balance """ try: - return from_wei(self.w3.eth.get_balance(self.w3.toChecksumAddress(self.account.address)), "ether") + callArgs=[] + callArgs.append(self.account.getUserAddress()) + callArgs.append("0x2::sui::SUI") + + result=rpc_call_sui_function(self.url, callArgs, method="suix_getBalance")["totalBalance"] + return fromSuiBase(result) except Exception as e: raise(Exception("Failed to get balance, Exception: {}".format(e))) + def get_usdc_coins(self): - callArgs=[] - callArgs.append(self.account.getUserAddress()) - callArgs.append(self.contracts.get_currency_type()) - result=rpc_get_usdc_coins("https://fullnode.testnet.sui.io:443",callArgs ) - return result + try: + callArgs=[] + callArgs.append(self.account.getUserAddress()) + callArgs.append(self.contracts.get_currency_type()) + result=rpc_call_sui_function(self.url,callArgs, method="suix_getCoins" ) + return result + except Exception as e: + raise(Exception("Failed to get USDC coins, Exception: {}".format(e))) - async def get_usdc_balance(self): + async def get_usdc_balance(self)-> float: """ Returns user's USDC token balance on Firefly. """ try: - contract = self.contracts.get_contract(name="USDC") - raw_bal = contract.functions.balanceOf(self.account.address).call() - return from_wei(int(raw_bal), "mwei") + callArgs=[] + callArgs.append(self.account.getUserAddress()) + callArgs.append(self.contracts.get_currency_type()) + result=rpc_call_sui_function(self.url, callArgs, method="suix_getBalance")["totalBalance"] + return fromSuiBase(result) + except Exception as e: raise(Exception("Failed to get balance, Exception: {}".format(e))) - async def get_margin_bank_balance(self): + async def get_margin_bank_balance(self)-> float: """ Returns user's Margin Bank balance. - """ + """ try: - contract = self.contracts.get_contract(name="MarginBank") - return from_wei(contract.functions.getAccountBankBalance(self.account.address).call(),"ether") + callArgs=[] + callArgs.append(self.contracts.get_bank_table_id()) + callArgs.append({ + "type": "address", + "value": self.account.getUserAddress() + }) + result=rpc_call_sui_function(self.url, callArgs, method="suix_getDynamicFieldObject") + + balance=fromSuiBase(result["data"]["content"]["fields"]["value"]["fields"]["balance"]) + return balance except Exception as e: raise(Exception("Failed to get balance, Exception: {}".format(e))) diff --git a/src/bluefin_client_sui/constants.py b/src/bluefin_client_sui/constants.py index 610349d..11a29b1 100644 --- a/src/bluefin_client_sui/constants.py +++ b/src/bluefin_client_sui/constants.py @@ -1,6 +1,6 @@ Networks = { "SUI_STAGING":{ - "url":"", + "url":"https://fullnode.testnet.sui.io:443", "chainId":1234, "apiGateway":"https://dapi.api.sui-staging.bluefin.io", "socketURL":"wss://dapi.api.sui-staging.bluefin.io", diff --git a/src/bluefin_client_sui/contracts.py b/src/bluefin_client_sui/contracts.py index 2e8d204..75c1936 100644 --- a/src/bluefin_client_sui/contracts.py +++ b/src/bluefin_client_sui/contracts.py @@ -9,6 +9,14 @@ def set_contract_addresses(self,contract_address,market=None,name=None): self.contract_addresses["auxiliaryContractsAddresses"]=contract_address['auxiliaryContractsAddresses'] self.contract_addresses[market]=contract_address[market.value] + def get_sub_account_id(self): + return self.contract_addresses['auxiliaryContractsAddresses']['objects']['SubAccounts']['id'] + + def get_bank_table_id(self): + return self.contract_addresses['auxiliaryContractsAddresses']['objects']['BankTable']['id'] + + def get_price_oracle_object_id(self, market): + return self.contract_addresses[market]['PriceOracle']['id'] def get_perpetual_id(self, market: MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): return self.contract_addresses[market]['Perpetual']['id'] @@ -18,7 +26,7 @@ def get_package_id(self, market: MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): def get_currency_type(self): return self.contract_addresses['auxiliaryContractsAddresses']['objects']['Currency']['dataType'] - def get_bank_id(self, market: MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): + def get_bank_id(self): return self.contract_addresses['auxiliaryContractsAddresses']['objects']['Bank']['id'] def get_contract(self,name,market=""): """ diff --git a/src/bluefin_client_sui/rpc.py b/src/bluefin_client_sui/rpc.py index 2afb631..0a51af7 100644 --- a/src/bluefin_client_sui/rpc.py +++ b/src/bluefin_client_sui/rpc.py @@ -46,7 +46,7 @@ def rpc_sui_executeTransactionBlock(url, txBytes, signature): return result -def rpc_get_usdc_coins(url, params, method="suix_getCoins"): +def rpc_call_sui_function(url, params, method="suix_getCoins"): base_dict={} base_dict["jsonrpc"]="2.0" base_dict["id"]= 1 diff --git a/src/bluefin_client_sui/utilities.py b/src/bluefin_client_sui/utilities.py index 77abeeb..10d2942 100644 --- a/src/bluefin_client_sui/utilities.py +++ b/src/bluefin_client_sui/utilities.py @@ -16,6 +16,10 @@ def fromDapiBase(number: Union[int, float], dtype=int)-> int: def toSuiBase(number: Union[int,float]) -> int: return int(number*SUI_BASE_NUM) +def fromSuiBase(number: Union[str,int])-> float: + number=float(number) + return number/float(SUI_BASE_NUM) + def numberToHex(num, pad=32): #converting number to Hexadecimal format From c30b610c481d2fe18a38ecbf7596bb15dae01ba0 Mon Sep 17 00:00:00 2001 From: Yasir Ejaz <105588724+yasir7ca@users.noreply.github.com> Date: Sun, 27 Aug 2023 13:00:07 +0400 Subject: [PATCH 08/12] Add sui-prod to networks (#7) * Add sui-prod to networks * remove eth specific constants * Revert "remove eth specific constants" This reverts commit 14c446f0bf814ff385e01e3a46a5cc3582068583. * Remove eth specific constants * remove eth abi specific code --- README.md | 18 +- src/bluefin_client_sui/__init__.py | 3 +- src/bluefin_client_sui/client.py | 66 +- src/bluefin_client_sui/constants.py | 80 +- src/bluefin_client_sui/contract_abis.py | 2116 ----------------------- src/bluefin_client_sui/contracts.py | 1 - src/bluefin_client_sui/enumerations.py | 6 - src/bluefin_client_sui/order_signer.py | 4 +- 8 files changed, 28 insertions(+), 2266 deletions(-) delete mode 100644 src/bluefin_client_sui/contract_abis.py diff --git a/README.md b/README.md index 9a3758b..5f77e23 100644 --- a/README.md +++ b/README.md @@ -29,20 +29,17 @@ pip install . The package currently supports python `>=3.8`. Find complete documentation on the library at https://docs.firefly.exchange/. - - - ### Getting Started When initializing the client, users must accept [terms and conditions](https://firefly.exchange/terms-of-use) and define network object containing the following values: ```json { - "apiGateway":"https://dapi.api.sui-staging.bluefin.io", - "socketURL":"wss://dapi.api.sui-staging.bluefin.io", - "dmsURL":"https://dapi.api.sui-staging.bluefin.io", - "webSocketURL":"wss://notifications.api.sui-staging.bluefin.io", - "onboardingUrl": "https://testnet.bluefin.io" + "apiGateway": "https://dapi.api.sui-staging.bluefin.io", + "socketURL": "wss://dapi.api.sui-staging.bluefin.io", + "dmsURL": "https://dapi.api.sui-staging.bluefin.io", + "webSocketURL": "wss://notifications.api.sui-staging.bluefin.io", + "onboardingUrl": "https://testnet.bluefin.io" } ``` @@ -92,6 +89,7 @@ if __name__ == "__main__": **Read-only Initialization:** Firefly-client can also be initialized in `read-only` mode, below is the example: + ```python from config import TEST_ACCT_KEY, TEST_NETWORK from bluefin_client_sui import FireflyClient, Networks @@ -106,7 +104,7 @@ async def main(): ) # Initializing client for the private key provided. The second argument api_token is optional - await client.init(True,"54b0bfafc9a48728f76e52848a716e96d490263392e3959c2d44f05dea960761") + await client.init(True,"54b0bfafc9a48728f76e52848a716e96d490263392e3959c2d44f05dea960761") # close aio http connection await client.apis.close_session() @@ -119,6 +117,7 @@ if __name__ == "__main__": loop.run_until_complete(main()) loop.close() ``` + ​Here is the [list](https://docs.bluefin.io/8/2.readonly-access-data) of APIs that can be accessed in `read-only` mode. **Placing Orders:** @@ -262,4 +261,3 @@ if __name__ == "__main__": loop.run_until_complete(main()) loop.close() ``` - diff --git a/src/bluefin_client_sui/__init__.py b/src/bluefin_client_sui/__init__.py index 90bac63..5387318 100644 --- a/src/bluefin_client_sui/__init__.py +++ b/src/bluefin_client_sui/__init__.py @@ -3,5 +3,4 @@ from .constants import * from .enumerations import * from .interfaces import * -from .utilities import * -from .contract_abis import * +from .utilities import * \ No newline at end of file diff --git a/src/bluefin_client_sui/client.py b/src/bluefin_client_sui/client.py index 4dcc3b5..6628c04 100644 --- a/src/bluefin_client_sui/client.py +++ b/src/bluefin_client_sui/client.py @@ -18,7 +18,6 @@ class FireflyClient: def __init__(self, are_terms_accepted, network, private_key=""): self.are_terms_accepted = are_terms_accepted self.network = network - self.w3 = ''#self._connect_w3(self.network["url"]) if private_key != "": #currently we only support seed phrase self.account=SuiWallet(seed=private_key) @@ -77,8 +76,7 @@ async def onboard_user(self, token:str=None): response = await self.authorize_signed_hash(onboarding_signature) if 'error' in response: - - raise SystemError("Authorization error: {}".format(response['error']['message'])) + raise SystemError(f"Authorization error: {response['error']['message']}") user_auth_token = response['token'] @@ -100,7 +98,7 @@ async def authorize_signed_hash(self, signed_hash:str): "isTermAccepted": self.are_terms_accepted, }) - def add_market(self, symbol: MARKET_SYMBOLS, trader_contract=None): + def add_market(self, symbol: MARKET_SYMBOLS): """ Adds Order signer for market to instance's order_signers dict. Inputs: @@ -112,9 +110,9 @@ def add_market(self, symbol: MARKET_SYMBOLS, trader_contract=None): """ symbol_str = symbol.value # if signer for market already exists return false - if (symbol_str in self.order_signers): - return False - + if symbol_str in self.order_signers: + return False + # if orders contract address is not provided get # from addresses retrieved from dapi #if trader_contract == None: @@ -123,28 +121,8 @@ def add_market(self, symbol: MARKET_SYMBOLS, trader_contract=None): # except: # raise SystemError("Can't find orders contract address for market: {}".format(symbol_str)) - self.order_signers[symbol_str] = OrderSigner( - self.network["chainId"], - ) - return True - - def add_contract(self,name,address,market=None): - """ - Adds contracts to the instance's contracts dictionary. - The contract name should match the contract's abi name in ./abi directory or a new abi should be added with the desired name. - Inputs: - name(str): The contract name. - address(str): The contract address. - market(str): The market (ETH/BTC) this contract belongs to (required for market specific contracts). - """ - abi = self.contracts.get_contract_abi(name) - if market: - contract=self.w3.eth.contract(address=Web3.toChecksumAddress(address), abi=abi) - self.contracts.set_contracts(market=market,name=name,contract=contract) - else: - contract=self.w3.eth.contract(address=Web3.toChecksumAddress(address), abi=abi) - self.contracts.set_contracts(name=name,contract=contract) - return + self.order_signers[symbol_str] = OrderSigner() + return True def create_order_to_sign(self, params:OrderSignatureRequest): """ @@ -979,9 +957,9 @@ async def reset_cancel_on_disconnect_timer(self, params:PostTimerAttributes): ) # check for service unavailibility if hasattr(response, 'status') and response.status == 503: - raise Exception("Cancel on Disconnect (dead-mans-switch) feature is currently unavailable") + raise Exception("Cancel on Disconnect (dead-mans-switch) feature is currently unavailable") return response - + ## Internal methods def _get_order_signer(self,symbol:MARKET_SYMBOLS=None): """ @@ -999,32 +977,8 @@ def _get_order_signer(self,symbol:MARKET_SYMBOLS=None): else: return self.order_signers - def _execute_tx(self, transaction): - """ - An internal function to create signed tx and wait for its receipt - Args: - transaction: A constructed txn using self.account address - - Returns: - tx_receipt: a receipt of txn mined on-chain - """ - tx_create = self.w3.eth.account.signTransaction(transaction, self.account.key) - tx_hash = self.w3.eth.sendRawTransaction(tx_create.rawTransaction) - return self.w3.eth.waitForTransactionReceipt(tx_hash) - - def _connect_w3(self,url): - """ - Creates a connection to Web3 RPC given the RPC url. - """ - try: - return Web3(Web3.HTTPProvider(url)) - except: - raise(Exception("Failed to connect to Host: {}".format(url))) - - async def close_connections(self): - # close aio http connection + # close aio http connection await self.apis.close_session() await self.dmsApi.close_session() - \ No newline at end of file diff --git a/src/bluefin_client_sui/constants.py b/src/bluefin_client_sui/constants.py index 11a29b1..3f76621 100644 --- a/src/bluefin_client_sui/constants.py +++ b/src/bluefin_client_sui/constants.py @@ -1,7 +1,6 @@ Networks = { "SUI_STAGING":{ "url":"https://fullnode.testnet.sui.io:443", - "chainId":1234, "apiGateway":"https://dapi.api.sui-staging.bluefin.io", "socketURL":"wss://dapi.api.sui-staging.bluefin.io", "dmsURL":"https://dapi.api.sui-staging.bluefin.io", @@ -9,63 +8,16 @@ "onboardingUrl": "https://testnet.bluefin.io", }, - - "DEV": { - "url": "https://l2-dev.firefly.exchange/", - "chainId": 78602, - "apiGateway": "https://dapi-dev.firefly.exchange", - "socketURL": "wss://dapi-dev.firefly.exchange", - "onboardingUrl": "https://dev.firefly.exchange", - }, - - "TESTNET_ARBITRUM": { - "url": "https://goerli-rollup.arbitrum.io/rpc", - "chainId": 421613, - "apiGateway": "https://dapi.api.arbitrum-staging.firefly.exchange", - "dmsURL": "https://api.arbitrum-staging.firefly.exchange/dead-man-switch", - "socketURL": "wss://dapi.api.arbitrum-staging.firefly.exchange", - "webSocketURL": "wss://notifications.api.arbitrum-staging.firefly.exchange", - "onboardingUrl": "https://testnet.firefly.exchange", - "UUID": "uuid-default" - }, - - "MAINNET_BOBA": { - "url": "https://bobabeam.boba.network/", - "chainId": 1294, - "apiGateway": "https://dapi.firefly.exchange", - "socketURL": "wss://dapi.firefly.exchange", - "onboardingUrl": "https://trade.firefly.exchange", - }, - "MAINNET_ARBITRUM": { - "url": "https://arb1.arbitrum.io/rpc/", - "chainId": 42161, - "apiGateway": "https://dapi.api.arbitrum-prod.firefly.exchange", - "dmsURL": "https://api.arbitrum-prod.firefly.exchange/dead-man-switch", - "socketURL": "wss://dapi.api.arbitrum-prod.firefly.exchange", - "webSocketURL": "wss://notifications.api.arbitrum-prod.firefly.exchange", - "onboardingUrl": "https://trade-arb.firefly.exchange", - "UUID": "uuid-default" - }, + "SUI_PROD":{ + "url":"https://fullnode.testnet.sui.io:443", + "apiGateway":"https://dapi.api.sui-prod.bluefin.io", + "socketURL":"wss://dapi.api.sui-prod.bluefin.io", + "dmsURL":"https://dapi.api.sui-prod.bluefin.io", + "webSocketURL":"wss://notifications.api.sui-prod.bluefin.io", + "onboardingUrl": "https://trade.bluefin.io", + } } - -EIP712_DOMAIN_NAME = "IsolatedTrader" - - -EIP712_DOMAIN_STRING = "EIP712Domain(string name,string version,uint128 chainId,address verifyingContract)" - - -EIP712_ORDER_STRUCT_STRING = \ - "Order(" + \ - "bytes8 flags," + \ - "uint128 quantity," + \ - "uint128 price," + \ - "uint128 triggerPrice," + \ - "uint128 leverage," + \ - "address maker," + \ - "uint128 expiration" + \ - ")" - ORDER_FLAGS = { "IS_BUY":1, "IS_DECREASE_ONLY": 2 @@ -116,22 +68,6 @@ }, } -EIP712_CANCEL_ORDER_STRUCT_STRING ="CancelLimitOrder(string action,bytes32[] orderHashes)" - -EIP712_ONBOARDING_ACTION_STRUCT_STRING = \ - 'firefly(' + \ - 'string action,' + \ - 'string onlySignOn' + \ - ')' - -EIP712_DOMAIN_STRING_NO_CONTRACT = \ - "EIP712Domain(" + \ - "string name," + \ - "string version," + \ - "uint128 chainId" + \ - ")" - - SUI_BASE_NUM=1000000000 DAPI_BASE_NUM=1000000000000000000 \ No newline at end of file diff --git a/src/bluefin_client_sui/contract_abis.py b/src/bluefin_client_sui/contract_abis.py deleted file mode 100644 index ed0d791..0000000 --- a/src/bluefin_client_sui/contract_abis.py +++ /dev/null @@ -1,2116 +0,0 @@ -## ABIs of contracts to be used by client - -MarginBank = { - "_format": "hh-sol-artifact-1", - "contractName": "MarginBank", - "sourceName": "contracts/MarginBank.sol", - "abi": [ - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "enum MarginBank.Action", - "name": "action", - "type": "uint8" - }, - { - "indexed": True, - "internalType": "address", - "name": "srcAddress", - "type": "address" - }, - { - "indexed": True, - "internalType": "address", - "name": "destAddress", - "type": "address" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "amount", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "srcBalance", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "destBalance", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "timestamp", - "type": "uint128" - } - ], - "name": "BankBalanceUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": False, - "internalType": "address", - "name": "destination", - "type": "address" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "amount", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "balance", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "timestamp", - "type": "uint128" - } - ], - "name": "BankTransferToPerpetual", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": False, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "MarginBankOperatorUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": True, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "bankBalances", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "bankOperators", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "candidate", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - }, - { - "internalType": "uint128", - "name": "_amount", - "type": "uint128" - } - ], - "name": "depositToBank", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "getAccountBankBalance", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "guardianContract", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_guardian", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "isWithdrawalAllowed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "_approved", - "type": "bool" - } - ], - "name": "setBankOperator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "setOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "enum Types.GuardianStatus", - "name": "_newStatus", - "type": "uint8" - } - ], - "name": "setWithdrawalStatus", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "token", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - }, - { - "internalType": "address", - "name": "_destination", - "type": "address" - }, - { - "internalType": "uint128", - "name": "_amount", - "type": "uint128" - } - ], - "name": "transferMarginToAccount", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "updateOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_destination", - "type": "address" - }, - { - "internalType": "uint128", - "name": "_amount", - "type": "uint128" - } - ], - "name": "withdrawFromBank", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x608060405234801561001057600080fd5b50611946806100206000396000f3fe608060405234801561001057600080fd5b506004361061010b5760003560e01c80637b1657c7116100a257806397a5e1441161007157806397a5e1441461025d5780639aacb467146102655780639ff5c88914610288578063bc5920ba1461029b578063fc0c546a146102a357600080fd5b80637b1657c7146101c65780637c7a7596146101d95780638b1681a6146102235780638da5cb5b1461024c57600080fd5b8063419dd428116100de578063419dd42814610173578063485cc955146101865780636c8381f814610199578063715018a6146101be57600080fd5b8063053834ee1461011057806313af4035146101385780632bce5f251461014d5780632c428e4e14610160575b600080fd5b61012361011e3660046115ad565b6102b6565b60405190151581526020015b60405180910390f35b61014b6101463660046114e9565b61040d565b005b61014b61015b366004611535565b610595565b61014b61016e3660046115f2565b6107b1565b61014b6101813660046115ad565b61088a565b61014b610194366004611503565b610a23565b6066546001600160a01b03165b6040516001600160a01b03909116815260200161012f565b61014b610b27565b61014b6101d4366004611577565b610ba7565b61020b6101e73660046114e9565b6001600160a01b0316600090815260cd60205260409020546001600160801b031690565b6040516001600160801b03909116815260200161012f565b61020b6102313660046114e9565b60cd602052600090815260409020546001600160801b031681565b6065546001600160a01b03166101a6565b610123610c60565b6101236102733660046114e9565b60cc6020526000908152604090205460ff1681565b60ce546101a6906001600160a01b031681565b61014b610c95565b60cb546101a6906001600160a01b031681565b6000600260015414156102e45760405162461bcd60e51b81526004016102db906117c6565b60405180910390fd5b600260015560cb5461030a906001600160a01b03163330856001600160801b0316610dd5565b6103198264e8d4a51000611837565b6001600160a01b038416600090815260cd60205260408120805490919061034a9084906001600160801b03166117fd565b92506101000a8154816001600160801b0302191690836001600160801b03160217905550826001600160a01b031661037f3390565b6001600160a01b03167f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f60006103ba8664e8d4a51000611837565b33600090815260cd6020526040808220546001600160a01b038b168352918190205490516103f99493926001600160801b03908116921690429061165c565b60405180910390a350600180805592915050565b6065546001600160a01b031633146104375760405162461bcd60e51b81526004016102db906116e0565b6001600160a01b0381166104985760405162461bcd60e51b815260206004820152602260248201527f46464c5946694f776e61626c65557067726164653a207a65726f206164647265604482015261737360f01b60648201526084016102db565b6065546001600160a01b03828116911614156105055760405162461bcd60e51b815260206004820152602660248201527f46464c5946694f776e61626c65557067726164653a2073616d65206173206f726044820152651a59da5b985b60d21b60648201526084016102db565b6066546001600160a01b03828116911614156105735760405162461bcd60e51b815260206004820152602760248201527f46464c5946694f776e61626c65557067726164653a2073616d652061732063616044820152666e64696461746560c81b60648201526084016102db565b606680546001600160a01b0319166001600160a01b0392909216919091179055565b600260015414156105b85760405162461bcd60e51b81526004016102db906117c6565b600260015533600090815260cc602052604090205460ff1661062e5760405162461bcd60e51b815260206004820152602960248201527f4d617267696e42616e6b3a2063616c6c6572206973206e6f7420612062616e6b6044820152681037b832b930ba37b960b91b60648201526084016102db565b6001600160a01b038316600090815260cd6020908152604091829020548251808401909352601a83527f496e73756666696369656e74206163636f756e742066756e64730000000000009183019190915261069c916001600160801b03808516921691909110159085610e46565b6001600160a01b038316600090815260cd6020526040812080548392906106cd9084906001600160801b0316611866565b82546101009290920a6001600160801b038181021990931691831602179091556001600160a01b038416600090815260cd6020526040812080548594509092610718918591166117fd565b82546101009290920a6001600160801b038181021990931691831602179091556001600160a01b03858116600081815260cd60205260408082205493881680835291205490945090927f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f9260029287929182169116425b6040516107a095949392919061165c565b60405180910390a350506001805550565b600260015414156107d45760405162461bcd60e51b81526004016102db906117c6565b600260015560ce546001600160a01b0316331461084b5760405162461bcd60e51b815260206004820152602f60248201527f4d617267696e42616e6b3a2063616c6c6572206973206e6f742074686520677560448201526e185c991a585b8818dbdb9d1c9858dd608a1b60648201526084016102db565b60ce805482919060ff60a01b1916600160a01b83600181111561087e57634e487b7160e01b600052602160045260246000fd5b02179055505060018055565b600260015414156108ad5760405162461bcd60e51b81526004016102db906117c6565b6002600155336108bb610c60565b6109215760405162461bcd60e51b815260206004820152603160248201527f4d617267696e42616e6b3a205769746864726177616c73206e6f7420616c6c6f6044820152701dd95908185d081d1a19481b5bdb595b9d607a1b60648201526084016102db565b60cb54610941906001600160a01b0316846001600160801b038516610e8c565b6109508264e8d4a51000611837565b6001600160a01b038216600090815260cd6020526040812080549091906109819084906001600160801b0316611866565b92506101000a8154816001600160801b0302191690836001600160801b03160217905550826001600160a01b0316816001600160a01b03167f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f60018564e8d4a510006109ed9190611837565b6001600160a01b03868116600090815260cd602052604080822054928b1682529020546001600160801b0391821691164261078f565b600054610100900460ff1615808015610a435750600054600160ff909116105b80610a5d5750303b158015610a5d575060005460ff166001145b610a795760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015610a9c576000805461ff0019166101001790555b60cb80546001600160a01b038086166001600160a01b03199283161790925560ce805492851692909116919091179055610ad4610ebc565b610adc610f8f565b8015610b22576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b6065546001600160a01b03163314610b515760405162461bcd60e51b81526004016102db906116e0565b6065546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3606580546001600160a01b0319908116909155606680549091169055565b6065546001600160a01b03163314610bd15760405162461bcd60e51b81526004016102db906116e0565b60026001541415610bf45760405162461bcd60e51b81526004016102db906117c6565b60026001556001600160a01b038216600081815260cc6020908152604091829020805460ff19168515159081179091558251938452908301527f23a3d102fca7a9f251ba156fa3583ea391662039e1e0279b951ebd17dbf58bc3910160405180910390a1505060018055565b60008060ce54600160a01b900460ff166001811115610c8f57634e487b7160e01b600052602160045260246000fd5b14905090565b6066546001600160a01b0316610d055760405162461bcd60e51b815260206004820152602f60248201527f46464c5946694f776e61626c65557067726164653a2063616e6469646174652060448201526e6973207a65726f206164647265737360881b60648201526084016102db565b6066546001600160a01b03163314610d6f5760405162461bcd60e51b815260206004820152602760248201527f46464c5946694f776e61626c65557067726164653a206e6f7420746865206e656044820152663b9037bbb732b960c91b60648201526084016102db565b6066546065546040516001600160a01b0392831692909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a360668054606580546001600160a01b03199081166001600160a01b03841617909155169055565b6040516001600160a01b0380851660248301528316604482015260648101829052610e409085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610fc0565b50505050565b82610b225781610e5582611092565b604051602001610e6692919061162d565b60408051601f198184030181529082905262461bcd60e51b82526102db916004016116ad565b6040516001600160a01b038316602482015260448101829052610b2290849063a9059cbb60e01b90606401610e09565b600054610100900460ff1615808015610edc5750600054600160ff909116105b80610ef65750303b158015610ef6575060005460ff166001145b610f125760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015610f35576000805461ff0019166101001790555b610f3d6111b5565b610f456111dc565b8015610f8c576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15b50565b600054610100900460ff16610fb65760405162461bcd60e51b81526004016102db9061177b565b610fbe6112da565b565b6000611015826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166113079092919063ffffffff16565b805190915015610b22578080602001905181019061103391906115d6565b610b225760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016102db565b6040517f3a20307830303030303030302e2e2e3030303030303030000000000000000000602082015260609060009060370160408051601f1981840301815291905290506001600160a01b03831660a0602060045b600c8110156111aa576110fb60048461188e565b925061110860048361188e565b915061111584841c611320565b85828151811061113557634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a90535061115784831c611320565b8561116383600b61181f565b8151811061118157634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a905350806111a2816118d1565b9150506110e7565b509295945050505050565b600054610100900460ff16610fbe5760405162461bcd60e51b81526004016102db9061177b565b600054610100900460ff16158080156111fc5750600054600160ff909116105b806112165750303b158015611216575060005460ff166001145b6112325760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015611255576000805461ff0019166101001790555b606580546001600160a01b0319163390811790915560405181906000907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a3508015610f8c576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb384740249890602001610f83565b600054610100900460ff166113015760405162461bcd60e51b81526004016102db9061177b565b60018055565b6060611316848460008561134c565b90505b9392505050565b6000600f8216600a8110611335576057611338565b60305b611342908261181f565b60f81b9392505050565b6060824710156113ad5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016102db565b6001600160a01b0385163b6114045760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016102db565b600080866001600160a01b031685876040516114209190611611565b60006040518083038185875af1925050503d806000811461145d576040519150601f19603f3d011682016040523d82523d6000602084013e611462565b606091505b509150915061147282828661147d565b979650505050505050565b6060831561148c575081611319565b82511561149c5782518084602001fd5b8160405162461bcd60e51b81526004016102db91906116ad565b80356001600160a01b03811681146114cd57600080fd5b919050565b80356001600160801b03811681146114cd57600080fd5b6000602082840312156114fa578081fd5b611319826114b6565b60008060408385031215611515578081fd5b61151e836114b6565b915061152c602084016114b6565b90509250929050565b600080600060608486031215611549578081fd5b611552846114b6565b9250611560602085016114b6565b915061156e604085016114d2565b90509250925092565b60008060408385031215611589578182fd5b611592836114b6565b915060208301356115a281611902565b809150509250929050565b600080604083850312156115bf578182fd5b6115c8836114b6565b915061152c602084016114d2565b6000602082840312156115e7578081fd5b815161131981611902565b600060208284031215611603578081fd5b813560028110611319578182fd5b600082516116238184602087016118a5565b9190910192915050565b6000835161163f8184602088016118a5565b8351908301906116538183602088016118a5565b01949350505050565b60a081016003871061167e57634e487b7160e01b600052602160045260246000fd5b9581526001600160801b0394851660208201529284166040840152908316606083015290911660809091015290565b60208152600082518060208401526116cc8160408501602087016118a5565b601f01601f19169190910160400192915050565b6020808252602d908201527f46464c5946694f776e61626c65557067726164653a2063616c6c65722069732060408201526c3737ba103a34329037bbb732b960991b606082015260800190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60006001600160801b03808316818516808303821115611653576116536118ec565b60008219821115611832576118326118ec565b500190565b60006001600160801b038083168185168183048111821515161561185d5761185d6118ec565b02949350505050565b60006001600160801b0383811690831681811015611886576118866118ec565b039392505050565b6000828210156118a0576118a06118ec565b500390565b60005b838110156118c05781810151838201526020016118a8565b83811115610e405750506000910152565b60006000198214156118e5576118e56118ec565b5060010190565b634e487b7160e01b600052601160045260246000fd5b8015158114610f8c57600080fdfea26469706673582212202ba8a44886c5de0596cc4ec7928626d84ae6af7670c4e7feb4666098a9bb9f9964736f6c63430008040033", - "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061010b5760003560e01c80637b1657c7116100a257806397a5e1441161007157806397a5e1441461025d5780639aacb467146102655780639ff5c88914610288578063bc5920ba1461029b578063fc0c546a146102a357600080fd5b80637b1657c7146101c65780637c7a7596146101d95780638b1681a6146102235780638da5cb5b1461024c57600080fd5b8063419dd428116100de578063419dd42814610173578063485cc955146101865780636c8381f814610199578063715018a6146101be57600080fd5b8063053834ee1461011057806313af4035146101385780632bce5f251461014d5780632c428e4e14610160575b600080fd5b61012361011e3660046115ad565b6102b6565b60405190151581526020015b60405180910390f35b61014b6101463660046114e9565b61040d565b005b61014b61015b366004611535565b610595565b61014b61016e3660046115f2565b6107b1565b61014b6101813660046115ad565b61088a565b61014b610194366004611503565b610a23565b6066546001600160a01b03165b6040516001600160a01b03909116815260200161012f565b61014b610b27565b61014b6101d4366004611577565b610ba7565b61020b6101e73660046114e9565b6001600160a01b0316600090815260cd60205260409020546001600160801b031690565b6040516001600160801b03909116815260200161012f565b61020b6102313660046114e9565b60cd602052600090815260409020546001600160801b031681565b6065546001600160a01b03166101a6565b610123610c60565b6101236102733660046114e9565b60cc6020526000908152604090205460ff1681565b60ce546101a6906001600160a01b031681565b61014b610c95565b60cb546101a6906001600160a01b031681565b6000600260015414156102e45760405162461bcd60e51b81526004016102db906117c6565b60405180910390fd5b600260015560cb5461030a906001600160a01b03163330856001600160801b0316610dd5565b6103198264e8d4a51000611837565b6001600160a01b038416600090815260cd60205260408120805490919061034a9084906001600160801b03166117fd565b92506101000a8154816001600160801b0302191690836001600160801b03160217905550826001600160a01b031661037f3390565b6001600160a01b03167f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f60006103ba8664e8d4a51000611837565b33600090815260cd6020526040808220546001600160a01b038b168352918190205490516103f99493926001600160801b03908116921690429061165c565b60405180910390a350600180805592915050565b6065546001600160a01b031633146104375760405162461bcd60e51b81526004016102db906116e0565b6001600160a01b0381166104985760405162461bcd60e51b815260206004820152602260248201527f46464c5946694f776e61626c65557067726164653a207a65726f206164647265604482015261737360f01b60648201526084016102db565b6065546001600160a01b03828116911614156105055760405162461bcd60e51b815260206004820152602660248201527f46464c5946694f776e61626c65557067726164653a2073616d65206173206f726044820152651a59da5b985b60d21b60648201526084016102db565b6066546001600160a01b03828116911614156105735760405162461bcd60e51b815260206004820152602760248201527f46464c5946694f776e61626c65557067726164653a2073616d652061732063616044820152666e64696461746560c81b60648201526084016102db565b606680546001600160a01b0319166001600160a01b0392909216919091179055565b600260015414156105b85760405162461bcd60e51b81526004016102db906117c6565b600260015533600090815260cc602052604090205460ff1661062e5760405162461bcd60e51b815260206004820152602960248201527f4d617267696e42616e6b3a2063616c6c6572206973206e6f7420612062616e6b6044820152681037b832b930ba37b960b91b60648201526084016102db565b6001600160a01b038316600090815260cd6020908152604091829020548251808401909352601a83527f496e73756666696369656e74206163636f756e742066756e64730000000000009183019190915261069c916001600160801b03808516921691909110159085610e46565b6001600160a01b038316600090815260cd6020526040812080548392906106cd9084906001600160801b0316611866565b82546101009290920a6001600160801b038181021990931691831602179091556001600160a01b038416600090815260cd6020526040812080548594509092610718918591166117fd565b82546101009290920a6001600160801b038181021990931691831602179091556001600160a01b03858116600081815260cd60205260408082205493881680835291205490945090927f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f9260029287929182169116425b6040516107a095949392919061165c565b60405180910390a350506001805550565b600260015414156107d45760405162461bcd60e51b81526004016102db906117c6565b600260015560ce546001600160a01b0316331461084b5760405162461bcd60e51b815260206004820152602f60248201527f4d617267696e42616e6b3a2063616c6c6572206973206e6f742074686520677560448201526e185c991a585b8818dbdb9d1c9858dd608a1b60648201526084016102db565b60ce805482919060ff60a01b1916600160a01b83600181111561087e57634e487b7160e01b600052602160045260246000fd5b02179055505060018055565b600260015414156108ad5760405162461bcd60e51b81526004016102db906117c6565b6002600155336108bb610c60565b6109215760405162461bcd60e51b815260206004820152603160248201527f4d617267696e42616e6b3a205769746864726177616c73206e6f7420616c6c6f6044820152701dd95908185d081d1a19481b5bdb595b9d607a1b60648201526084016102db565b60cb54610941906001600160a01b0316846001600160801b038516610e8c565b6109508264e8d4a51000611837565b6001600160a01b038216600090815260cd6020526040812080549091906109819084906001600160801b0316611866565b92506101000a8154816001600160801b0302191690836001600160801b03160217905550826001600160a01b0316816001600160a01b03167f362dc4437434742f4bfcbefa9a40555fb345c70bd06af722e8e3f736d932ef8f60018564e8d4a510006109ed9190611837565b6001600160a01b03868116600090815260cd602052604080822054928b1682529020546001600160801b0391821691164261078f565b600054610100900460ff1615808015610a435750600054600160ff909116105b80610a5d5750303b158015610a5d575060005460ff166001145b610a795760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015610a9c576000805461ff0019166101001790555b60cb80546001600160a01b038086166001600160a01b03199283161790925560ce805492851692909116919091179055610ad4610ebc565b610adc610f8f565b8015610b22576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b6065546001600160a01b03163314610b515760405162461bcd60e51b81526004016102db906116e0565b6065546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3606580546001600160a01b0319908116909155606680549091169055565b6065546001600160a01b03163314610bd15760405162461bcd60e51b81526004016102db906116e0565b60026001541415610bf45760405162461bcd60e51b81526004016102db906117c6565b60026001556001600160a01b038216600081815260cc6020908152604091829020805460ff19168515159081179091558251938452908301527f23a3d102fca7a9f251ba156fa3583ea391662039e1e0279b951ebd17dbf58bc3910160405180910390a1505060018055565b60008060ce54600160a01b900460ff166001811115610c8f57634e487b7160e01b600052602160045260246000fd5b14905090565b6066546001600160a01b0316610d055760405162461bcd60e51b815260206004820152602f60248201527f46464c5946694f776e61626c65557067726164653a2063616e6469646174652060448201526e6973207a65726f206164647265737360881b60648201526084016102db565b6066546001600160a01b03163314610d6f5760405162461bcd60e51b815260206004820152602760248201527f46464c5946694f776e61626c65557067726164653a206e6f7420746865206e656044820152663b9037bbb732b960c91b60648201526084016102db565b6066546065546040516001600160a01b0392831692909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a360668054606580546001600160a01b03199081166001600160a01b03841617909155169055565b6040516001600160a01b0380851660248301528316604482015260648101829052610e409085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610fc0565b50505050565b82610b225781610e5582611092565b604051602001610e6692919061162d565b60408051601f198184030181529082905262461bcd60e51b82526102db916004016116ad565b6040516001600160a01b038316602482015260448101829052610b2290849063a9059cbb60e01b90606401610e09565b600054610100900460ff1615808015610edc5750600054600160ff909116105b80610ef65750303b158015610ef6575060005460ff166001145b610f125760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015610f35576000805461ff0019166101001790555b610f3d6111b5565b610f456111dc565b8015610f8c576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15b50565b600054610100900460ff16610fb65760405162461bcd60e51b81526004016102db9061177b565b610fbe6112da565b565b6000611015826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166113079092919063ffffffff16565b805190915015610b22578080602001905181019061103391906115d6565b610b225760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016102db565b6040517f3a20307830303030303030302e2e2e3030303030303030000000000000000000602082015260609060009060370160408051601f1981840301815291905290506001600160a01b03831660a0602060045b600c8110156111aa576110fb60048461188e565b925061110860048361188e565b915061111584841c611320565b85828151811061113557634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a90535061115784831c611320565b8561116383600b61181f565b8151811061118157634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a905350806111a2816118d1565b9150506110e7565b509295945050505050565b600054610100900460ff16610fbe5760405162461bcd60e51b81526004016102db9061177b565b600054610100900460ff16158080156111fc5750600054600160ff909116105b806112165750303b158015611216575060005460ff166001145b6112325760405162461bcd60e51b81526004016102db9061172d565b6000805460ff191660011790558015611255576000805461ff0019166101001790555b606580546001600160a01b0319163390811790915560405181906000907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a3508015610f8c576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb384740249890602001610f83565b600054610100900460ff166113015760405162461bcd60e51b81526004016102db9061177b565b60018055565b6060611316848460008561134c565b90505b9392505050565b6000600f8216600a8110611335576057611338565b60305b611342908261181f565b60f81b9392505050565b6060824710156113ad5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016102db565b6001600160a01b0385163b6114045760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016102db565b600080866001600160a01b031685876040516114209190611611565b60006040518083038185875af1925050503d806000811461145d576040519150601f19603f3d011682016040523d82523d6000602084013e611462565b606091505b509150915061147282828661147d565b979650505050505050565b6060831561148c575081611319565b82511561149c5782518084602001fd5b8160405162461bcd60e51b81526004016102db91906116ad565b80356001600160a01b03811681146114cd57600080fd5b919050565b80356001600160801b03811681146114cd57600080fd5b6000602082840312156114fa578081fd5b611319826114b6565b60008060408385031215611515578081fd5b61151e836114b6565b915061152c602084016114b6565b90509250929050565b600080600060608486031215611549578081fd5b611552846114b6565b9250611560602085016114b6565b915061156e604085016114d2565b90509250925092565b60008060408385031215611589578182fd5b611592836114b6565b915060208301356115a281611902565b809150509250929050565b600080604083850312156115bf578182fd5b6115c8836114b6565b915061152c602084016114d2565b6000602082840312156115e7578081fd5b815161131981611902565b600060208284031215611603578081fd5b813560028110611319578182fd5b600082516116238184602087016118a5565b9190910192915050565b6000835161163f8184602088016118a5565b8351908301906116538183602088016118a5565b01949350505050565b60a081016003871061167e57634e487b7160e01b600052602160045260246000fd5b9581526001600160801b0394851660208201529284166040840152908316606083015290911660809091015290565b60208152600082518060208401526116cc8160408501602087016118a5565b601f01601f19169190910160400192915050565b6020808252602d908201527f46464c5946694f776e61626c65557067726164653a2063616c6c65722069732060408201526c3737ba103a34329037bbb732b960991b606082015260800190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60006001600160801b03808316818516808303821115611653576116536118ec565b60008219821115611832576118326118ec565b500190565b60006001600160801b038083168185168183048111821515161561185d5761185d6118ec565b02949350505050565b60006001600160801b0383811690831681811015611886576118866118ec565b039392505050565b6000828210156118a0576118a06118ec565b500390565b60005b838110156118c05781810151838201526020016118a8565b83811115610e405750506000910152565b60006000198214156118e5576118e56118ec565b5060010190565b634e487b7160e01b600052601160045260246000fd5b8015158114610f8c57600080fdfea26469706673582212202ba8a44886c5de0596cc4ec7928626d84ae6af7670c4e7feb4666098a9bb9f9964736f6c63430008040033", - "linkReferences": {}, - "deployedLinkReferences": {} -} - -Perpetual = { - "_format": "hh-sol-artifact-1", - "contractName": "Perpetual", - "sourceName": "contracts/Perpetual.sol", - "abi": [ - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": False, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "components": [ - { - "internalType": "bool", - "name": "isPosPositive", - "type": "bool" - }, - { - "internalType": "uint128", - "name": "mro", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "qPos", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "margin", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "oiOpen", - "type": "uint128" - } - ], - "indexed": False, - "internalType": "struct Types.PositionBalance", - "name": "position", - "type": "tuple" - }, - { - "indexed": False, - "internalType": "enum Perpetual.Action", - "name": "action", - "type": "uint8" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "timestamp", - "type": "uint128" - } - ], - "name": "AccountPositionUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "components": [ - { - "internalType": "bool", - "name": "isPosPositive", - "type": "bool" - }, - { - "internalType": "uint128", - "name": "mro", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "qPos", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "margin", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "oiOpen", - "type": "uint128" - } - ], - "indexed": False, - "internalType": "struct Types.PositionBalance", - "name": "balance", - "type": "tuple" - }, - { - "indexed": False, - "internalType": "bool", - "name": "settlementIsPositive", - "type": "bool" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "settlementAmount", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "price", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "int256", - "name": "fundingRate", - "type": "int256" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "timestamp", - "type": "uint128" - } - ], - "name": "AccountSettlementUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "uint128", - "name": "defaultMakerFee", - "type": "uint128" - } - ], - "name": "DefaultMakerFeeUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "uint128", - "name": "defaultTakerFee", - "type": "uint128" - } - ], - "name": "DefaultTakerFeeUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "feePool", - "type": "address" - } - ], - "name": "FeePoolUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "funder", - "type": "address" - } - ], - "name": "FundingOracleUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "int256", - "name": "", - "type": "int256" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "name": "GlobalIndexUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "int256", - "name": "initialMargin", - "type": "int256" - } - ], - "name": "IMRUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "int256", - "name": "maintenanceMargin", - "type": "int256" - } - ], - "name": "MMRUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "marginBank", - "type": "address" - } - ], - "name": "MarginBankUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "provider", - "type": "address" - } - ], - "name": "OffchainFROperatorUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": True, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "uint128", - "name": "settlementPrice", - "type": "uint128" - } - ], - "name": "PerpetualDelisted", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "amount", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "timestamp", - "type": "uint128" - } - ], - "name": "PositionClosed", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "oracle", - "type": "address" - } - ], - "name": "PriceOracleUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": False, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "SettlementOperatorUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": False, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": False, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "SubAccountUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "tradeContract", - "type": "address" - }, - { - "indexed": False, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "TradeContractUpdate", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "indexed": True, - "internalType": "address", - "name": "maker", - "type": "address" - }, - { - "indexed": True, - "internalType": "address", - "name": "taker", - "type": "address" - }, - { - "indexed": False, - "internalType": "bytes32", - "name": "makerOrderHash", - "type": "bytes32" - }, - { - "indexed": False, - "internalType": "bytes32", - "name": "takerOrderHash", - "type": "bytes32" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "makerMRO", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "takerMRO", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "makerFee", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "takerFee", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "int256", - "name": "makerPnl", - "type": "int256" - }, - { - "indexed": False, - "internalType": "int256", - "name": "takerPnl", - "type": "int256" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "tradeQuantity", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "price", - "type": "uint128" - }, - { - "indexed": False, - "internalType": "bool", - "name": "isBuy", - "type": "bool" - }, - { - "indexed": False, - "internalType": "uint128", - "name": "timestamp", - "type": "uint128" - } - ], - "name": "TradeExecuted", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "uint128", - "name": "startTime", - "type": "uint128" - } - ], - "name": "TradingStarted", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": False, - "internalType": "uint128", - "name": "stopTime", - "type": "uint128" - } - ], - "name": "TradingStopped", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - }, - { - "internalType": "uint128", - "name": "_amount", - "type": "uint128" - } - ], - "name": "addMargin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "addresses", - "outputs": [ - { - "internalType": "address", - "name": "marginMath", - "type": "address" - }, - { - "internalType": "address", - "name": "oracle", - "type": "address" - }, - { - "internalType": "address", - "name": "funder", - "type": "address" - }, - { - "internalType": "address", - "name": "marginBank", - "type": "address" - }, - { - "internalType": "address", - "name": "evaluator", - "type": "address" - }, - { - "internalType": "address", - "name": "feePool", - "type": "address" - }, - { - "internalType": "address", - "name": "guardian", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - }, - { - "internalType": "uint128", - "name": "_leverage", - "type": "uint128" - } - ], - "name": "adjustLeverage", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "candidate", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "closePosition", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "defaultMakerFee", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "defaultTakerFee", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "_priceLowerBound", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "_priceUpperBound", - "type": "uint128" - } - ], - "name": "delistPerpetual", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "delisted", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "delistingPrice", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "getAccountBalance", - "outputs": [ - { - "components": [ - { - "internalType": "bool", - "name": "isPosPositive", - "type": "bool" - }, - { - "internalType": "uint128", - "name": "mro", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "qPos", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "margin", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "oiOpen", - "type": "uint128" - } - ], - "internalType": "struct Types.PositionBalance", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "getIsSubAccount", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "globalIndex", - "outputs": [ - { - "internalType": "uint128", - "name": "timestamp", - "type": "uint128" - }, - { - "internalType": "int256", - "name": "value", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "hasAccountPermissions", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "initialMarginRequired", - "outputs": [ - { - "internalType": "int256", - "name": "", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_marketName", - "type": "string" - }, - { - "components": [ - { - "internalType": "address", - "name": "marginMath", - "type": "address" - }, - { - "internalType": "address", - "name": "oracle", - "type": "address" - }, - { - "internalType": "address", - "name": "funder", - "type": "address" - }, - { - "internalType": "address", - "name": "marginBank", - "type": "address" - }, - { - "internalType": "address", - "name": "evaluator", - "type": "address" - }, - { - "internalType": "address", - "name": "feePool", - "type": "address" - }, - { - "internalType": "address", - "name": "guardian", - "type": "address" - } - ], - "internalType": "struct IPerpetual.Addresses", - "name": "_addresses", - "type": "tuple" - }, - { - "internalType": "address", - "name": "_trustedForwarder", - "type": "address" - }, - { - "internalType": "int256", - "name": "_initialMargin", - "type": "int256" - }, - { - "internalType": "int256", - "name": "_maintenanceMargin", - "type": "int256" - }, - { - "internalType": "uint128", - "name": "_defaultMakerFee", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "_defaultTakerFee", - "type": "uint128" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "isTradingAllowed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "forwarder", - "type": "address" - } - ], - "name": "isTrustedForwarder", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maintenanceMarginRequired", - "outputs": [ - { - "internalType": "int256", - "name": "", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "marketName", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "msgSender", - "outputs": [ - { - "internalType": "address payable", - "name": "ret", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - }, - { - "internalType": "uint128", - "name": "_amount", - "type": "uint128" - } - ], - "name": "removeMargin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "_defaultMakerFee", - "type": "uint128" - } - ], - "name": "setDefaultMakerFee", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "_defaultTakerFee", - "type": "uint128" - } - ], - "name": "setDefaultTakerFee", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_feePool", - "type": "address" - } - ], - "name": "setFeePool", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_funder", - "type": "address" - } - ], - "name": "setFunder", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "setFundingRate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "int256", - "name": "_initialMargin", - "type": "int256" - } - ], - "name": "setInitialMargin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "int256", - "name": "_maintenanceMargin", - "type": "int256" - } - ], - "name": "setMaintenanceMargin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_marginBank", - "type": "address" - } - ], - "name": "setMarginBank", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newOperator", - "type": "address" - } - ], - "name": "setOffChainFROperator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "int256", - "name": "offchainFundingRate", - "type": "int256" - } - ], - "name": "setOffChainFundingRate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_oracle", - "type": "address" - } - ], - "name": "setOracle", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "setOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "_approved", - "type": "bool" - } - ], - "name": "setSettlementOperator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "_approved", - "type": "bool" - } - ], - "name": "setSubAccount", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_trader", - "type": "address" - }, - { - "internalType": "bool", - "name": "_approved", - "type": "bool" - } - ], - "name": "setTradeContract", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "enum Types.GuardianStatus", - "name": "_newStatus", - "type": "uint8" - } - ], - "name": "setTradingStatus", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "settlementOperators", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "_startingTime", - "type": "uint128" - } - ], - "name": "startTrading", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "stopTrading", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "_accounts", - "type": "address[]" - }, - { - "components": [ - { - "internalType": "uint128", - "name": "takerIndex", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "makerIndex", - "type": "uint128" - }, - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IPerpetual.TradeArg[]", - "name": "_trades", - "type": "tuple[]" - }, - { - "internalType": "uint128", - "name": "gasCharges", - "type": "uint128" - } - ], - "name": "trade", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "tradeContracts", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "tradingStartTime", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "trustedForwarder", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "updateOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ] -} - -USDC = { - "_format": "hh-sol-artifact-1", - "contractName": "DummyUSDC", - "sourceName": "contracts/mock/DummyUSDC.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "_name", - "type": "string" - }, - { - "internalType": "string", - "name": "_symbol", - "type": "string" - }, - { - "internalType": "uint128", - "name": "_initialSupply", - "type": "uint128" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": True, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": False, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": False, - "inputs": [ - { - "indexed": True, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": True, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": False, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_recipient", - "type": "address" - }, - { - "internalType": "uint128", - "name": "_amount", - "type": "uint128" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x60806040523480156200001157600080fd5b5060405162000db238038062000db28339810160408190526200003491620002cb565b8251839083906200004d90600390602085019062000172565b5080516200006390600490602084019062000172565b5050506200008133826001600160801b03166200008a60201b60201c565b505050620003cc565b6001600160a01b038216620000e55760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015260640160405180910390fd5b8060026000828254620000f9919062000354565b90915550506001600160a01b038216600090815260208190526040812080548392906200012890849062000354565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b828054620001809062000379565b90600052602060002090601f016020900481019282620001a45760008555620001ef565b82601f10620001bf57805160ff1916838001178555620001ef565b82800160010185558215620001ef579182015b82811115620001ef578251825591602001919060010190620001d2565b50620001fd92915062000201565b5090565b5b80821115620001fd576000815560010162000202565b600082601f83011262000229578081fd5b81516001600160401b0380821115620002465762000246620003b6565b604051601f8301601f19908116603f01168101908282118183101715620002715762000271620003b6565b816040528381526020925086838588010111156200028d578485fd5b8491505b83821015620002b0578582018301518183018401529082019062000291565b83821115620002c157848385830101525b9695505050505050565b600080600060608486031215620002e0578283fd5b83516001600160401b0380821115620002f7578485fd5b620003058783880162000218565b945060208601519150808211156200031b578384fd5b506200032a8682870162000218565b604086015190935090506001600160801b038116811462000349578182fd5b809150509250925092565b600082198211156200037457634e487b7160e01b81526011600452602481fd5b500190565b600181811c908216806200038e57607f821691505b60208210811415620003b057634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052604160045260246000fd5b6109d680620003dc6000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c806370a082311161007157806370a082311461014157806395d89b411461016a578063a457c2d714610172578063a9059cbb14610185578063be29184f14610198578063dd62ed3e146101ad57600080fd5b806306fdde03146100b9578063095ea7b3146100d757806318160ddd146100fa57806323b872dd1461010c578063313ce5671461011f578063395093511461012e575b600080fd5b6100c16101c0565b6040516100ce91906108ee565b60405180910390f35b6100ea6100e53660046108c5565b610252565b60405190151581526020016100ce565b6002545b6040519081526020016100ce565b6100ea61011a366004610849565b61026a565b604051600681526020016100ce565b6100ea61013c3660046108c5565b61028e565b6100fe61014f3660046107f6565b6001600160a01b031660009081526020819052604090205490565b6100c16102b0565b6100ea6101803660046108c5565b6102bf565b6100ea6101933660046108c5565b61033f565b6101ab6101a6366004610884565b61034d565b005b6100fe6101bb366004610817565b610364565b6060600380546101cf90610965565b80601f01602080910402602001604051908101604052809291908181526020018280546101fb90610965565b80156102485780601f1061021d57610100808354040283529160200191610248565b820191906000526020600020905b81548152906001019060200180831161022b57829003601f168201915b5050505050905090565b60003361026081858561038f565b5060019392505050565b6000336102788582856104b3565b61028385858561052d565b506001949350505050565b6000336102608185856102a18383610364565b6102ab9190610941565b61038f565b6060600480546101cf90610965565b600033816102cd8286610364565b9050838110156103325760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b610283828686840361038f565b60003361026081858561052d565b61036082826001600160801b03166106fb565b5050565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103f15760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610329565b6001600160a01b0382166104525760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610329565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104bf8484610364565b90506000198114610527578181101561051a5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610329565b610527848484840361038f565b50505050565b6001600160a01b0383166105915760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610329565b6001600160a01b0382166105f35760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610329565b6001600160a01b0383166000908152602081905260409020548181101561066b5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610329565b6001600160a01b038085166000908152602081905260408082208585039055918516815290812080548492906106a2908490610941565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516106ee91815260200190565b60405180910390a3610527565b6001600160a01b0382166107515760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610329565b80600260008282546107639190610941565b90915550506001600160a01b03821660009081526020819052604081208054839290610790908490610941565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b80356001600160a01b03811681146107f157600080fd5b919050565b600060208284031215610807578081fd5b610810826107da565b9392505050565b60008060408385031215610829578081fd5b610832836107da565b9150610840602084016107da565b90509250929050565b60008060006060848603121561085d578081fd5b610866846107da565b9250610874602085016107da565b9150604084013590509250925092565b60008060408385031215610896578182fd5b61089f836107da565b915060208301356001600160801b03811681146108ba578182fd5b809150509250929050565b600080604083850312156108d7578182fd5b6108e0836107da565b946020939093013593505050565b6000602080835283518082850152825b8181101561091a578581018301518582016040015282016108fe565b8181111561092b5783604083870101525b50601f01601f1916929092016040019392505050565b6000821982111561096057634e487b7160e01b81526011600452602481fd5b500190565b600181811c9082168061097957607f821691505b6020821081141561099a57634e487b7160e01b600052602260045260246000fd5b5091905056fea26469706673582212204090fc316510124c41761aa38bf0dc2852042412d8cf0472384c08d04f427a3c64736f6c63430008040033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100b45760003560e01c806370a082311161007157806370a082311461014157806395d89b411461016a578063a457c2d714610172578063a9059cbb14610185578063be29184f14610198578063dd62ed3e146101ad57600080fd5b806306fdde03146100b9578063095ea7b3146100d757806318160ddd146100fa57806323b872dd1461010c578063313ce5671461011f578063395093511461012e575b600080fd5b6100c16101c0565b6040516100ce91906108ee565b60405180910390f35b6100ea6100e53660046108c5565b610252565b60405190151581526020016100ce565b6002545b6040519081526020016100ce565b6100ea61011a366004610849565b61026a565b604051600681526020016100ce565b6100ea61013c3660046108c5565b61028e565b6100fe61014f3660046107f6565b6001600160a01b031660009081526020819052604090205490565b6100c16102b0565b6100ea6101803660046108c5565b6102bf565b6100ea6101933660046108c5565b61033f565b6101ab6101a6366004610884565b61034d565b005b6100fe6101bb366004610817565b610364565b6060600380546101cf90610965565b80601f01602080910402602001604051908101604052809291908181526020018280546101fb90610965565b80156102485780601f1061021d57610100808354040283529160200191610248565b820191906000526020600020905b81548152906001019060200180831161022b57829003601f168201915b5050505050905090565b60003361026081858561038f565b5060019392505050565b6000336102788582856104b3565b61028385858561052d565b506001949350505050565b6000336102608185856102a18383610364565b6102ab9190610941565b61038f565b6060600480546101cf90610965565b600033816102cd8286610364565b9050838110156103325760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b610283828686840361038f565b60003361026081858561052d565b61036082826001600160801b03166106fb565b5050565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103f15760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610329565b6001600160a01b0382166104525760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610329565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104bf8484610364565b90506000198114610527578181101561051a5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610329565b610527848484840361038f565b50505050565b6001600160a01b0383166105915760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610329565b6001600160a01b0382166105f35760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610329565b6001600160a01b0383166000908152602081905260409020548181101561066b5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610329565b6001600160a01b038085166000908152602081905260408082208585039055918516815290812080548492906106a2908490610941565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516106ee91815260200190565b60405180910390a3610527565b6001600160a01b0382166107515760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610329565b80600260008282546107639190610941565b90915550506001600160a01b03821660009081526020819052604081208054839290610790908490610941565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b80356001600160a01b03811681146107f157600080fd5b919050565b600060208284031215610807578081fd5b610810826107da565b9392505050565b60008060408385031215610829578081fd5b610832836107da565b9150610840602084016107da565b90509250929050565b60008060006060848603121561085d578081fd5b610866846107da565b9250610874602085016107da565b9150604084013590509250925092565b60008060408385031215610896578182fd5b61089f836107da565b915060208301356001600160801b03811681146108ba578182fd5b809150509250929050565b600080604083850312156108d7578182fd5b6108e0836107da565b946020939093013593505050565b6000602080835283518082850152825b8181101561091a578581018301518582016040015282016108fe565b8181111561092b5783604083870101525b50601f01601f1916929092016040019392505050565b6000821982111561096057634e487b7160e01b81526011600452602481fd5b500190565b600181811c9082168061097957607f821691505b6020821081141561099a57634e487b7160e01b600052602260045260246000fd5b5091905056fea26469706673582212204090fc316510124c41761aa38bf0dc2852042412d8cf0472384c08d04f427a3c64736f6c63430008040033", - "linkReferences": {}, - "deployedLinkReferences": {} -} diff --git a/src/bluefin_client_sui/contracts.py b/src/bluefin_client_sui/contracts.py index 75c1936..c59b3f9 100644 --- a/src/bluefin_client_sui/contracts.py +++ b/src/bluefin_client_sui/contracts.py @@ -1,4 +1,3 @@ -from .contract_abis import MarginBank, Perpetual, USDC from .enumerations import * class Contracts: def __init__(self): diff --git a/src/bluefin_client_sui/enumerations.py b/src/bluefin_client_sui/enumerations.py index 2430861..ef0d818 100644 --- a/src/bluefin_client_sui/enumerations.py +++ b/src/bluefin_client_sui/enumerations.py @@ -11,12 +11,6 @@ class ORDER_SIDE(Enum): class MARKET_SYMBOLS(Enum): BTC = "BTC-PERP" ETH = "ETH-PERP" - SOL = "SOL-PERP" - LINK = "LINK-PERP" - MATIC = "MATIC-PERP" - DOGE = "DOGE-PERP" - ARB = "ARB-PERP" - class TIME_IN_FORCE(Enum): FILL_OR_KILL = "FOK" diff --git a/src/bluefin_client_sui/order_signer.py b/src/bluefin_client_sui/order_signer.py index eb9e2ca..ac38a9f 100644 --- a/src/bluefin_client_sui/order_signer.py +++ b/src/bluefin_client_sui/order_signer.py @@ -3,12 +3,10 @@ from .signer import Signer from .interfaces import Order import hashlib -import json class OrderSigner(Signer): - def __init__(self, network_id, orders_contract_address="", domain="IsolatedTrader", version="1.0"): + def __init__(self, orders_contract_address="", domain="IsolatedTrader", version="1.0"): super().__init__() - self.network_id = network_id self.contract_address = orders_contract_address self.domain = domain self.version = version From 779fe70356fa485b57e544edcbbfaf054ce32ced Mon Sep 17 00:00:00 2001 From: Yasir Ejaz <105588724+yasir7ca@users.noreply.github.com> Date: Sun, 27 Aug 2023 15:05:42 +0400 Subject: [PATCH 09/12] move toSuiBase function inside of sign order method (#8) * move toSuiBase function inside of sign order method * Bump up version in pyproject.toml plus some refactoring --- CHANGES.md | 33 +- README.md | 22 +- examples/11.sub_accounts.py | 9 +- examples/12.open_order_event.py | 2 - examples/13.orderbook_updates.py | 21 +- .../16.listening_events_using_sub_account.py | 2 - examples/18.dms_api.py | 7 +- examples/4.placing_orders.py | 18 +- examples/6.adjusting_margin.py | 16 +- examples/7.cancelling_orders.py | 9 +- examples/9.user_data.py | 2 - examples/contract_call.py | 5 - pyproject.toml | 2 +- src/bluefin_client_sui/client.py | 335 ++++++++---------- src/bluefin_client_sui/constants.py | 7 +- src/bluefin_client_sui/contracts.py | 87 ++--- src/bluefin_client_sui/interfaces.py | 28 +- src/bluefin_client_sui/order_signer.py | 43 +-- src/bluefin_client_sui/utilities.py | 7 - 19 files changed, 240 insertions(+), 415 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 86191d9..1599d90 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,24 +1,25 @@ # Changes w.r.t old library + ## Initialisation change + 1. The way we initialise is still the same, the difference is that NOW we give `SEED_PHRASE` as `TEST_ACCT_KEY` 2. Currently we support test phrase only and we sign it using ED25519 - -## Placing/Cancelling Orders -1. Now while placing orders we need to ensure that price, quantity and leverage are sent in sui format. we have exposed a function `toSuiBase(value)` to convert our value to that format. -2. Same goes for cancelling orders. - ## Sockets + 1. Nothing is changes in sockets except for the url link -## Signing (internal) +## Signing (internal) + 1. We do not sign our transaction using ETH library anymore. We now use `nacl` and `hashlib` in order to sign the messages. 2. For signing orders we sign it using the following scheme + 1. Assuming we have the order with values in sui format. 2. We first get the `orderhash` of the order using the following method - ```python flags = self.get_order_flags(order) + + ```python flags = self.get_order_flags(order) flags = hexToByteArray(numberToHex(flags,2)) - + buffer=bytearray() orderPriceHex=hexToByteArray(numberToHex(int(order["price"]))) orderQuantityHex=hexToByteArray(numberToHex(int(order['quantity']))) @@ -30,32 +31,34 @@ bluefin=bytearray("Bluefin", encoding="utf-8") buffer=orderPriceHex+orderQuantityHex+orderLeverageHex+orderSalt+orderExpiration+orderMaker+orderMarket+flags+bluefin - ``` + ``` + 3. We then get the sha256 of the buffer.hex() 4. We then sign it and append '1' to it, specifying that we signed it using ed25519 + 3. For signing onboarding signer we follow a different approach. + 1. What is onboarding signer: When we are calling a firefly init function we sign a string using our private key and send it to bluefin exchange along with our public key, bluefin exchange verifies it and returns us a token. 2. For signing it we first convert our message to bytes and then add [3,0,0, len(message)] to the start of our bytearray and then our message. if our message length is greater than 256 then it wont fit in a byte in this case we follow BCS methodology to send our message. 4. For signing cancel order, there are two ways. 1. We first sign the order and send it to bluefin. Now we have the hash of the order. We first change our encode our hash to BCS format. - ```python + ```python sigDict={} sigDict['orderHashes']=order_hash encodedMessage=self.encode_message(sigDict) - ``` + ``` 2. Please have a look at signer.py to see the implementation or encode_message. 3. Then we sign the encodedMessage and send the signature to bluefin for cancelling order 5. For signing cancel order second method. 1. We sign the order and send it to bluefin, now imagine we do not have the hash of our order. 2. We resign our order and get the hash and then we follow the similar approach as above. Please have a look at `7.cancelling_orders.py` in our examples file. - - ## A detailed Guide on Onboarding: + 1. Basically as explained earlier when sign the onboarding url and send it to bluefin, bluefin returns us the TOKEN. -2. The change in this repo is following. -3. ```python +2. The change in this repo is following. +3. ```python # imagine msg="https://testnet.bluefin.io" msgDict={} msgDict['onboardingUrl']=msg diff --git a/README.md b/README.md index 5f77e23..401249a 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,11 @@ When initializing the client, users must accept [terms and conditions](https://f ```json { - "apiGateway": "https://dapi.api.sui-staging.bluefin.io", - "socketURL": "wss://dapi.api.sui-staging.bluefin.io", - "dmsURL": "https://dapi.api.sui-staging.bluefin.io", - "webSocketURL": "wss://notifications.api.sui-staging.bluefin.io", - "onboardingUrl": "https://testnet.bluefin.io" + "apiGateway": "https://dapi.api.sui-prod.bluefin.io", + "socketURL": "wss://dapi.api.sui-prod.bluefin.io", + "dmsURL": "https://dapi.api.sui-prod.bluefin.io", + "webSocketURL": "wss://notifications.api.sui-prod.bluefin.io", + "onboardingUrl": "https://trade.bluefin.io" } ``` @@ -49,7 +49,7 @@ Users can import predefined networks from [constants](https://github.com/firefly from bluefin_client_sui import Networks ``` -For testing purposes use `Networks[SUI_STAGING]` and for production please use `Networks[SUI_PROD]`. Currently only SUI_STAGING is available. +For testing purposes use `Networks[SUI_STAGING]` and for production please use `Networks[SUI_PROD]`. ## Initialization example​ @@ -125,7 +125,6 @@ if __name__ == "__main__": ```python from config import TEST_ACCT_KEY, TEST_NETWORK from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest -from bluefin_client_sui.utilities import toSuiBase import asyncio async def main(): @@ -138,19 +137,16 @@ async def main(): await client.init(True) - # add market that you wish to trade on ETH/BTC are supported currently - client.add_market(MARKET_SYMBOLS.ETH) - user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) # creates a LIMIT order to be signed signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol - price=toSuiBase(1900), # price at which you want to place order - quantity=toSuiBase(0.01), # quantity + price=1900, # price at which you want to place order + quantity=0.01, # quantity side=ORDER_SIDE.SELL, orderType=ORDER_TYPE.LIMIT, - leverage=toSuiBase(user_leverage) + leverage=user_leverage ) # create signed order diff --git a/examples/11.sub_accounts.py b/examples/11.sub_accounts.py index d0a8e3a..7fb68e9 100644 --- a/examples/11.sub_accounts.py +++ b/examples/11.sub_accounts.py @@ -20,10 +20,7 @@ async def main(): # # whitelist sub account status = await clientParent.update_sub_account(clientChild.get_public_address(), True) - print("Sub account created: {}".format(status)) - - - clientChild.add_market(MARKET_SYMBOLS.ETH) + print(f"Sub account created: {status}") parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) await clientParent.adjust_leverage(MARKET_SYMBOLS.ETH,1) @@ -32,10 +29,10 @@ async def main(): symbol=MARKET_SYMBOLS.ETH, # sub account is only whitelisted for ETH market maker=clientParent.get_public_address(), # maker of the order is the parent account price=0, - quantity=toSuiBase(0.02), + quantity=0.02, side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.MARKET, - leverage=toSuiBase(parent_leverage), + leverage=parent_leverage, ) # order is signed using sub account's private key diff --git a/examples/12.open_order_event.py b/examples/12.open_order_event.py index 75f07d2..8b94122 100644 --- a/examples/12.open_order_event.py +++ b/examples/12.open_order_event.py @@ -51,8 +51,6 @@ async def main(): client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - client.add_market(MARKET_SYMBOLS.ETH) - def callback(event): global event_received print("Event data:", event) diff --git a/examples/13.orderbook_updates.py b/examples/13.orderbook_updates.py index 5f6acfd..c3ff327 100644 --- a/examples/13.orderbook_updates.py +++ b/examples/13.orderbook_updates.py @@ -7,26 +7,24 @@ import time from config import TEST_ACCT_KEY, TEST_NETWORK from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest -from bluefin_client_sui.utilities import toSuiBase import asyncio TEST_NETWORK="SUI_STAGING" event_received = False async def place_limit_order(client:FireflyClient): - # default leverage of account is set to 3 on firefly user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) # creates a LIMIT order to be signed signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol - price=toSuiBase(1300), # price at which you want to place order - quantity=toSuiBase(0.01), # quantity - side=ORDER_SIDE.SELL, + price=1300, # price at which you want to place order + quantity=0.01, # quantity + side=ORDER_SIDE.SELL, orderType=ORDER_TYPE.LIMIT, - leverage= toSuiBase(1) - ) + leverage=user_leverage + ) # create signed order signed_order = client.create_signed_order(signature_request) @@ -43,8 +41,6 @@ async def main(): client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - client.add_market(MARKET_SYMBOLS.ETH) - def callback(event): global event_received print("Event data:", event) @@ -79,7 +75,6 @@ def callback(event): if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() - + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/16.listening_events_using_sub_account.py b/examples/16.listening_events_using_sub_account.py index 6c87c48..8be39ed 100644 --- a/examples/16.listening_events_using_sub_account.py +++ b/examples/16.listening_events_using_sub_account.py @@ -42,8 +42,6 @@ async def main(): print("Listening to parents position updates") await clientChild.socket.listen(SOCKET_EVENTS.POSITION_UPDATE.value, callback) - clientChild.add_market(MARKET_SYMBOLS.ETH) - parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) signature_request = OrderSignatureRequest( diff --git a/examples/18.dms_api.py b/examples/18.dms_api.py index 8db738e..cd28326 100644 --- a/examples/18.dms_api.py +++ b/examples/18.dms_api.py @@ -16,22 +16,19 @@ async def main(): await client.init(True) print("User: ", client.get_public_address()) - client.add_market(MARKET_SYMBOLS.BTC) - client.add_market(MARKET_SYMBOLS.ETH) - countDownsObject: PostTimerAttributes = dict() countDowns = list() countDowns.append({ 'symbol': MARKET_SYMBOLS.BTC.value, 'countDown': 3 * 1000 } - ) + ) countDowns.append({ 'symbol': MARKET_SYMBOLS.ETH.value, 'countDown': 3 * 1000 } - ) + ) countDownsObject["countDowns"] = countDowns try: diff --git a/examples/4.placing_orders.py b/examples/4.placing_orders.py index 6ca074e..3b37f3f 100644 --- a/examples/4.placing_orders.py +++ b/examples/4.placing_orders.py @@ -1,6 +1,5 @@ import sys,os sys.path.append(os.getcwd()+"/src/") -from bluefin_client_sui.utilities import toSuiBase from config import TEST_ACCT_KEY, TEST_NETWORK from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest import asyncio @@ -8,20 +7,18 @@ async def place_limit_order(client: FireflyClient): - # default leverage of account is set to 3 on firefly user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) print ("User Leverage", user_leverage) - # creates a LIMIT order to be signed signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol - price=toSuiBase(1636.8), # price at which you want to place order - quantity=toSuiBase(0.01), # quantity + price=1636.8, # price at which you want to place order + quantity=0.01, # quantity side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.LIMIT, - leverage=toSuiBase(user_leverage) + leverage=user_leverage ) # create signed order @@ -44,9 +41,9 @@ async def place_market_order(client: FireflyClient): signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, - price = toSuiBase(0), - quantity = toSuiBase(1), - leverage = toSuiBase(1), + price = 0, + quantity = 1, + leverage = 1, side = ORDER_SIDE.BUY, reduceOnly = False, postOnly = False, @@ -81,9 +78,6 @@ async def main(): await client.init(True) - # add market that you wish to trade on ETH/BTC are supported currently - client.add_market(MARKET_SYMBOLS.ETH) - # await place_limit_order(client) await (client) await place_limit_order(client) diff --git a/examples/6.adjusting_margin.py b/examples/6.adjusting_margin.py index 096c554..bde01a3 100644 --- a/examples/6.adjusting_margin.py +++ b/examples/6.adjusting_margin.py @@ -5,11 +5,10 @@ from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ADJUST_MARGIN import asyncio from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest -from bluefin_client_sui.utilities import toSuiBase TEST_NETWORK="SUI_STAGING" async def place_limit_order(client: FireflyClient): - + # default leverage of account is set to 3 on firefly user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) await client.adjust_leverage(MARKET_SYMBOLS.ETH, 3) @@ -21,11 +20,11 @@ async def place_limit_order(client: FireflyClient): # creates a LIMIT order to be signed signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol - price=toSuiBase(1636.8), # price at which you want to place order - quantity=toSuiBase(0.01), # quantity + price=1636.8, # price at which you want to place order + quantity=0.01, # quantity side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.LIMIT, - leverage=toSuiBase(user_leverage) + leverage=user_leverage ) # create signed order @@ -48,9 +47,9 @@ async def place_market_order(client: FireflyClient): signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, - price = toSuiBase(0), - quantity = toSuiBase(1), - leverage = toSuiBase(user_leverage), + price = 0, + quantity = 1, + leverage = user_leverage, side = ORDER_SIDE.BUY, orderType=ORDER_TYPE.MARKET, ) @@ -71,7 +70,6 @@ async def main(): client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - client.add_market(MARKET_SYMBOLS.ETH) print (await client.get_usdc_balance()) #usdc_coins=client.get_usdc_coins() diff --git a/examples/7.cancelling_orders.py b/examples/7.cancelling_orders.py index e29ca6b..5659cc9 100644 --- a/examples/7.cancelling_orders.py +++ b/examples/7.cancelling_orders.py @@ -3,7 +3,6 @@ import time from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui.utilities import toSuiBase from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, ORDER_STATUS, OrderSignatureRequest from pprint import pprint import asyncio @@ -15,8 +14,6 @@ async def main(): client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - # must add market before cancelling its orders - client.add_market(MARKET_SYMBOLS.ETH) #client.create_order_to_sign() await client.adjust_leverage(MARKET_SYMBOLS.ETH, 1) @@ -24,11 +21,11 @@ async def main(): # creates a LIMIT order to be signed order = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol - price=toSuiBase(1636.8), # price at which you want to place order - quantity=toSuiBase(0.01), # quantity + price=1636.8, # price at which you want to place order + quantity=0.01, # quantity side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.LIMIT, - leverage=toSuiBase(1), + leverage=1, salt=random.randint(0,100000000), expiration=int(time.time()+(30*24*60*60))*1000 ) diff --git a/examples/9.user_data.py b/examples/9.user_data.py index 5525e53..d9b88d3 100644 --- a/examples/9.user_data.py +++ b/examples/9.user_data.py @@ -38,8 +38,6 @@ async def main(): pprint(tx_history) # gets user current position - # must add market to client before using this method - client.add_market(MARKET_SYMBOLS.ETH) position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) print("User position:") diff --git a/examples/contract_call.py b/examples/contract_call.py index dbdd689..e6dbba8 100644 --- a/examples/contract_call.py +++ b/examples/contract_call.py @@ -20,11 +20,6 @@ async def main(): await client.withdraw_margin_from_bank(1000) - - - - # add market that you wish to trade on ETH/BTC are supported currently - client.add_market(MARKET_SYMBOLS.ETH) txBytes="AAADAQGPzuAZV5krLKJWD1WUXOjk7Guz2vki2pBMZJ6CXkRf1XLGgQAAAAAAAQAgH/qFdX+Vzs7ShiLTDkGkUsiFFt9TRQyxkTgS3YKNWWgAEOgDAAAAAAAAAAAAAAAAAAABAF82iNaL7Cly31h0P767ErFoKbQb8bxQeNNRAJDvbPWOC21hcmdpbl9iYW5rEndpdGhkcmF3X2Zyb21fYmFuawADAQAAAQEAAQIAH/qFdX+Vzs7ShiLTDkGkUsiFFt9TRQyxkTgS3YKNWWgBWiybg/9fRf7zXUmsdBU3umIFeucEZNWeMGzlLttPf86tHg8AAAAAACA3qZfzxP1+yVILtVkJ6LdfZvkF7gK877AJco9Xook9Th/6hXV/lc7O0oYi0w5BpFLIhRbfU0UMsZE4Et2CjVlo6AMAAAAAAAAA4fUFAAAAAAA=" dec_msg=base64.b64decode(txBytes) mysigner=signer.Signer() diff --git a/pyproject.toml b/pyproject.toml index 8f32b5c..75ebff0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "bluefin_client_sui" -version = "0.1.0" +version = "1.0.0" description = "Library to interact with firefly exchange protocol including its off-chain api-gateway and on-chain contracts" readme = "README.md" requires-python = ">=3.8" diff --git a/src/bluefin_client_sui/client.py b/src/bluefin_client_sui/client.py index 6628c04..40f6717 100644 --- a/src/bluefin_client_sui/client.py +++ b/src/bluefin_client_sui/client.py @@ -1,19 +1,21 @@ import json +from copy import deepcopy + from .api_service import APIService from .contracts import Contracts from .order_signer import OrderSigner from .onboarding_signer import OnboardingSigner -from .utilities import * from .constants import TIME, SERVICE_URLS -from .interfaces import * from .sockets_lib import Sockets -from .enumerations import * from .websocket_client import WebsocketClient -from .account import * from .signer import Signer +from .utilities import default_value from .rpc import * +from .account import * +from .interfaces import * +from .enumerations import * - +_SUI_BASE_NUM=1000000000 class FireflyClient: def __init__(self, are_terms_accepted, network, private_key=""): self.are_terms_accepted = are_terms_accepted @@ -23,37 +25,38 @@ def __init__(self, are_terms_accepted, network, private_key=""): self.account=SuiWallet(seed=private_key) #self.account = Account.from_key(private_key) self.apis = APIService(self.network["apiGateway"], default_value(self.network, "UUID", "") ) - self.dmsApi = APIService(self.network["dmsURL"]) + self.dms_api = APIService(self.network["dmsURL"]) self.socket = Sockets(self.network["socketURL"]) - self.webSocketClient = WebsocketClient(self.network["webSocketURL"]) + self.ws_client = WebsocketClient(self.network["webSocketURL"]) self.contracts = Contracts() - self.order_signers = {} + self.order_signer = OrderSigner() self.onboarding_signer = OnboardingSigner() self.contract_signer=Signer() self.url=self.network['url'] - async def init(self, user_onboarding=True, api_token="", symbol:MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): + async def init(self, user_onboarding=True, api_token=""): """ Initialize the client. Inputs: user_onboarding (bool, optional): If set to true onboards the user address to exchange and gets authToken. Defaults to True. api_token(string, optional): API token to initialize client in read-only mode """ - self.contracts.contract_addresses = await self.get_contract_addresses(symbol) - self.contracts.set_contract_addresses(self.contracts.contract_addresses, market=symbol) + contract_info = await self.get_contract_addresses() + self.contracts.set_contract_addresses(contract_info) + if api_token: self.apis.api_token = api_token # for socket self.socket.set_api_token(api_token) - self.webSocketClient.set_api_token(api_token) + self.ws_client.set_api_token(api_token) # In case of api_token received, user onboarding is not done elif user_onboarding: self.apis.auth_token = await self.onboard_user() - self.dmsApi.auth_token = self.apis.auth_token + self.dms_api.auth_token = self.apis.auth_token self.socket.set_token(self.apis.auth_token) - self.webSocketClient.set_token(self.apis.auth_token) + self.ws_client.set_token(self.apis.auth_token) async def onboard_user(self, token:str=None): @@ -98,32 +101,6 @@ async def authorize_signed_hash(self, signed_hash:str): "isTermAccepted": self.are_terms_accepted, }) - def add_market(self, symbol: MARKET_SYMBOLS): - """ - Adds Order signer for market to instance's order_signers dict. - Inputs: - symbol(MARKET_SYMBOLS): Market symbol of order signer. - trader_contract(str): Contract address of the Orders contract. - - Returns: - bool: indicating whether the market was successfully added - """ - symbol_str = symbol.value - # if signer for market already exists return false - if symbol_str in self.order_signers: - return False - - # if orders contract address is not provided get - # from addresses retrieved from dapi - #if trader_contract == None: - # try: - # trader_contract = self.contracts.contract_addresses[symbol_str]["IsolatedTrader"] - # except: - # raise SystemError("Can't find orders contract address for market: {}".format(symbol_str)) - - self.order_signers[symbol_str] = OrderSigner() - return True - def create_order_to_sign(self, params:OrderSignatureRequest): """ Creates order signature request for an order. @@ -133,7 +110,7 @@ def create_order_to_sign(self, params:OrderSignatureRequest): Returns: Order: order raw info """ - expiration = current_unix_timestamp() + expiration = current_unix_timestamp() # MARKET ORDER set expiration of 1 minute if (params["orderType"] == ORDER_TYPE.MARKET): expiration += TIME["SECONDS_IN_A_MINUTE"] @@ -142,9 +119,9 @@ def create_order_to_sign(self, params:OrderSignatureRequest): else: expiration += TIME["SECONDS_IN_A_MONTH"] expiration*=1000 - + return Order ( - market = default_value(params,'market',self.contracts.get_market_id(params['symbol'])), + market = default_value(params,'market',self.contracts.get_perpetual_id(params['symbol'])), isBuy = params["side"] == ORDER_SIDE.BUY, price = params["price"], quantity = params["quantity"], @@ -158,43 +135,36 @@ def create_order_to_sign(self, params:OrderSignatureRequest): ioc = default_value(params,"ioc", False ) ) - def create_signed_order(self, params:OrderSignatureRequest): + def create_signed_order(self, req:OrderSignatureRequest) -> OrderSignatureResponse: """ - Create an order from provided params and signs it using the private - key of the account + Create an order from provided params and signs it using the private key of the account Inputs: params (OrderSignatureRequest): parameters to create order with - Returns: OrderSignatureResponse: order raw info and generated signature """ - - # from params create order to sign - order = self.create_order_to_sign(params) - - symbol = params["symbol"].value - order_signer = self.order_signers.get(symbol) + sui_params = deepcopy(req) + sui_params["price"] = self._to_sui_base(req["price"]) + sui_params["quantity"] = self._to_sui_base(req["quantity"]) + sui_params["leverage"] = self._to_sui_base(req["leverage"]) - if not order_signer: - raise SystemError("Provided Market Symbol({}) is not added to client library".format(symbol)) - - order_signature = order_signer.sign_order(order, self.account.privateKeyBytes) - order_signature=order_signature+self.account.publicKeyBase64.decode() - + order = self.create_order_to_sign(sui_params) + symbol = sui_params["symbol"].value + order_signature = self.order_signer.sign_order(order, self.account.privateKeyBytes) + order_signature=order_signature+self.account.publicKeyBase64.decode() return OrderSignatureResponse( symbol=symbol, - price=params["price"], - quantity=params["quantity"], - side=params["side"], - leverage=default_value(params, "leverage", 1), - reduceOnly=default_value(params, "reduceOnly", False), + price=sui_params["price"], + quantity=sui_params["quantity"], + side=sui_params["side"], + leverage=default_value(sui_params, "leverage", self._to_sui_base(1)), + reduceOnly=default_value(sui_params, "reduceOnly", False), salt=order["salt"], expiration=order["expiration"], orderSignature=order_signature, - orderType=params["orderType"], + orderType=sui_params["orderType"], maker=order["maker"], - orderbookOnly=default_value(params,'orderbookOnly',True) - + orderbookOnly=default_value(sui_params,'orderbookOnly',True) ) def create_signed_cancel_order(self,params:OrderSignatureRequest, parentAddress:str=""): @@ -205,17 +175,13 @@ def create_signed_cancel_order(self,params:OrderSignatureRequest, parentAddress: Inputs: params (OrderSignatureRequest): parameters to create cancel order with parentAddress (str): Only provided by a sub account - + Returns: OrderSignatureResponse: generated cancel signature """ - try: - signer:OrderSigner = self._get_order_signer(params["symbol"]) - order_to_sign = self.create_order_to_sign(params) - hash = signer.get_order_hash(order_to_sign, withBufferHex=False) - return self.create_signed_cancel_orders(params["symbol"], hash.hex(), parentAddress) - except Exception as e: - return "" + order_to_sign = self.create_order_to_sign(params) + hash_val = self.order_signer.get_order_hash(order_to_sign, withBufferHex=False) + return self.create_signed_cancel_orders(params["symbol"], hash_val.hex(), parentAddress) def create_signed_cancel_orders(self, symbol:MARKET_SYMBOLS, order_hash:list, parentAddress:str=""): """ @@ -228,11 +194,10 @@ def create_signed_cancel_orders(self, symbol:MARKET_SYMBOLS, order_hash:list, pa Returns: OrderCancellationRequest: containing symbol, hashes and signature """ - if type(order_hash)!=list: + if isinstance(order_hash, list) is False: order_hash = [order_hash] - order_signer:OrderSigner = self._get_order_signer(symbol) - cancel_hash = order_signer.sign_cancellation_hash(order_hash ) - hash_sig = order_signer.sign_hash(cancel_hash,self.account.privateKeyBytes,"")+self.account.publicKeyBase64.decode() + cancel_hash = self.order_signer.encode_message({'orderHashes' : order_hash}) + hash_sig = self.order_signer.sign_hash(cancel_hash,self.account.privateKeyBytes,"")+self.account.publicKeyBase64.decode() return OrderCancellationRequest( symbol=symbol.value, hashes=order_hash, @@ -252,10 +217,10 @@ async def post_cancel_order(self,params:OrderCancellationRequest): return await self.apis.delete( SERVICE_URLS["ORDERS"]["ORDERS_HASH"], { - "symbol": params["symbol"], - "orderHashes":params["hashes"], - "cancelSignature":params["signature"], - "parentAddress": params["parentAddress"], + "symbol": params["symbol"], + "orderHashes":params["hashes"], + "cancelSignature":params["signature"], + "parentAddress": params["parentAddress"], }, auth_required=True ) @@ -324,7 +289,7 @@ async def post_signed_order(self, params:PlaceOrderRequest): ) ## Contract calls - async def deposit_margin_to_bank(self, amount: int, coin_id: str, market=MARKET_SYMBOLS.ETH)-> bool: + async def deposit_margin_to_bank(self, amount: int, coin_id: str) -> bool: """ Deposits given amount of USDC from user's account to margin bank @@ -334,12 +299,12 @@ async def deposit_margin_to_bank(self, amount: int, coin_id: str, market=MARKET_ Returns: Boolean: true if amount is successfully deposited, false otherwise """ - package_id=self.contracts.get_package_id(market=market) + package_id=self.contracts.get_package_id() user_address=self.account.getUserAddress() callArgs=[] callArgs.append(self.contracts.get_bank_id()) callArgs.append(self.account.getUserAddress()) - callArgs.append(str(amount)) + callArgs.append(str(self._to_sui_base(amount))) callArgs.append(coin_id) txBytes=rpc_unsafe_moveCall(self.url, callArgs, @@ -355,7 +320,7 @@ async def deposit_margin_to_bank(self, amount: int, coin_id: str, market=MARKET_ return res - async def withdraw_margin_from_bank(self, amount, market=MARKET_SYMBOLS.ETH): + async def withdraw_margin_from_bank(self, amount): """ Withdraws given amount of usdc from margin bank if possible @@ -375,7 +340,7 @@ async def withdraw_margin_from_bank(self, amount, market=MARKET_SYMBOLS.ETH): "withdraw_from_bank", "margin_bank", self.account.getUserAddress(), - self.contracts.get_package_id(market=market) + self.contracts.get_package_id() ) signature=self.contract_signer.sign_tx(txBytes, self.account) res=rpc_sui_executeTransactionBlock(self.url, @@ -384,25 +349,23 @@ async def withdraw_margin_from_bank(self, amount, market=MARKET_SYMBOLS.ETH): return res - async def withdraw_all_margin_from_bank(self, market=MARKET_SYMBOLS.ETH): + async def withdraw_all_margin_from_bank(self): bank_id=self.contracts.get_bank_id() account_address=self.account.getUserAddress() - perp_id=self.contracts.get_perpetual_id() - callArgs=[bank_id, account_address] txBytes=rpc_unsafe_moveCall(self.url, - callArgs, - "withdraw_all_margin_from_bank", - "margin_bank", - self.account.getUserAddress(), - self.contracts.get_package_id(market=market) - ) + callArgs, + "withdraw_all_margin_from_bank", + "margin_bank", + self.account.getUserAddress(), + self.contracts.get_package_id() + ) signature=self.contract_signer.sign_tx(txBytes, self.account) res=rpc_sui_executeTransactionBlock(self.url, - txBytes, - signature) - + txBytes, + signature + ) return res @@ -430,18 +393,18 @@ async def adjust_leverage(self, symbol, leverage, parentAddress:str=""): callArgs.append(self.contracts.get_bank_id()) callArgs.append(self.contracts.get_sub_account_id()) callArgs.append(account_address) - callArgs.append(str(toSuiBase(leverage))) + callArgs.append(str(self._to_sui_base(leverage))) callArgs.append(self.contracts.get_price_oracle_object_id(symbol.value)) - txBytes=rpc_unsafe_moveCall(self.url, - callArgs, - "adjust_leverage", - "exchange", - self.account.getUserAddress(), - self.contracts.get_package_id(market=symbol)) + txBytes=rpc_unsafe_moveCall(self.url, + callArgs, + "adjust_leverage", + "exchange", + self.account.getUserAddress(), + self.contracts.get_package_id() + ) signature=self.contract_signer.sign_tx(txBytes, self.account) result=rpc_sui_executeTransactionBlock(self.url, txBytes, signature) return result - else: await self.apis.post( SERVICE_URLS["USER"]["ADJUST_LEVERAGE"], @@ -450,12 +413,11 @@ async def adjust_leverage(self, symbol, leverage, parentAddress:str=""): "address": account_address, "leverage": toDapiBase(leverage), "marginType": MARGIN_TYPE.ISOLATED.value, - }, + }, auth_required=True - ) - + ) return True - + async def adjust_margin(self, symbol: MARKET_SYMBOLS, operation: ADJUST_MARGIN, amount: str, parentAddress:str=""): """ Adjusts user's on-chain position by adding or removing the specified amount of margin. @@ -474,35 +436,36 @@ async def adjust_margin(self, symbol: MARKET_SYMBOLS, operation: ADJUST_MARGIN, if(user_position == {}): - raise(Exception("User has no open position on market: {}".format(symbol))) - else: - callArgs = [] - callArgs.append(self.contracts.get_perpetual_id()) - callArgs.append(self.contracts.get_bank_id()) + raise(Exception(f"User has no open position on market: {symbol}")) - callArgs.append(self.contracts.get_sub_account_id()) - callArgs.append(self.account.getUserAddress()) - callArgs.append(str(amount)) - callArgs.append(self.contracts.get_price_oracle_object_id(symbol)) - if operation==ADJUST_MARGIN.ADD: - txBytes=rpc_unsafe_moveCall(self.url, - callArgs, - "add_margin", - "exchange", - self.account.getUserAddress(), - self.contracts.get_package_id()) - - else: - txBytes=rpc_unsafe_moveCall(self.url, - callArgs, - "remove_margin", - "exchange", - self.account.getUserAddress(), - self.contracts.get_package_id()) - - signature=self.contract_signer.sign_tx(txBytes, self.account) - result=rpc_sui_executeTransactionBlock(self.url,txBytes, signature) + callArgs = [] + callArgs.append(self.contracts.get_perpetual_id(symbol)) + callArgs.append(self.contracts.get_bank_id()) + + callArgs.append(self.contracts.get_sub_account_id()) + callArgs.append(self.account.getUserAddress()) + callArgs.append(str(amount)) + callArgs.append(self.contracts.get_price_oracle_object_id(symbol)) + if operation==ADJUST_MARGIN.ADD: + txBytes=rpc_unsafe_moveCall(self.url, + callArgs, + "add_margin", + "exchange", + self.account.getUserAddress(), + self.contracts.get_package_id() + ) + else: + txBytes=rpc_unsafe_moveCall(self.url, + callArgs, + "remove_margin", + "exchange", + self.account.getUserAddress(), + self.contracts.get_package_id() + ) + + signature=self.contract_signer.sign_tx(txBytes, self.account) + result=rpc_sui_executeTransactionBlock(self.url,txBytes, signature) return True @@ -521,14 +484,14 @@ async def update_sub_account(self, sub_account_address: str, status: bool) -> bo callArgs.append(sub_account_address) callArgs.append(status) txBytes=rpc_unsafe_moveCall(self.url, - callArgs, - "set_sub_account", - "roles", - self.account.getUserAddress(), - self.contracts.get_package_id()) + callArgs, + "set_sub_account", + "roles", + self.account.getUserAddress(), + self.contracts.get_package_id() + ) signature=self.contract_signer.sign_tx(txBytes, self.account) - result=rpc_sui_executeTransactionBlock(self.url, txBytes, signature) if result['result']['effects']['status']['status']=='success': return True @@ -545,13 +508,13 @@ async def get_native_chain_token_balance(self)-> float: callArgs.append("0x2::sui::SUI") result=rpc_call_sui_function(self.url, callArgs, method="suix_getBalance")["totalBalance"] - return fromSuiBase(result) + return self._from_sui_base(result) except Exception as e: - raise(Exception("Failed to get balance, Exception: {}".format(e))) - + raise(Exception(f"Failed to get balance, error: {e}")) + + def get_usdc_coins(self): try: - callArgs=[] callArgs.append(self.account.getUserAddress()) callArgs.append(self.contracts.get_currency_type()) @@ -569,32 +532,30 @@ async def get_usdc_balance(self)-> float: callArgs.append(self.account.getUserAddress()) callArgs.append(self.contracts.get_currency_type()) result=rpc_call_sui_function(self.url, callArgs, method="suix_getBalance")["totalBalance"] - return fromSuiBase(result) - + return self._from_sui_base(result) + except Exception as e: raise(Exception("Failed to get balance, Exception: {}".format(e))) async def get_margin_bank_balance(self)-> float: """ Returns user's Margin Bank balance. - """ + """ try: - - callArgs=[] - callArgs.append(self.contracts.get_bank_table_id()) - callArgs.append({ + call_args=[] + call_args.append(self.contracts.get_bank_table_id()) + call_args.append({ "type": "address", "value": self.account.getUserAddress() }) - result=rpc_call_sui_function(self.url, callArgs, method="suix_getDynamicFieldObject") + result=rpc_call_sui_function(self.url, call_args, method="suix_getDynamicFieldObject") - balance=fromSuiBase(result["data"]["content"]["fields"]["value"]["fields"]["balance"]) + balance=self._from_sui_base(result["data"]["content"]["fields"]["value"]["fields"]["balance"]) return balance except Exception as e: raise(Exception("Failed to get balance, Exception: {}".format(e))) ## Market endpoints - async def get_orderbook(self, params:GetOrderbookRequest): """ Returns a dictionary containing the orderbook snapshot. @@ -604,11 +565,10 @@ async def get_orderbook(self, params:GetOrderbookRequest): dict: Orderbook snapshot """ params = extract_enums(params, ["symbol"]) - return await self.apis.get( SERVICE_URLS["MARKET"]["ORDER_BOOK"], params - ) + ) async def get_exchange_status(self): """ @@ -784,25 +744,17 @@ async def get_market_recent_trades(self,params:GetMarketRecentTradesRequest): return await self.apis.get( SERVICE_URLS["MARKET"]["RECENT_TRADE"], params - ) + ) - async def get_contract_addresses(self, symbol:MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): + async def get_contract_addresses(self): """ - Returns all contract addresses for the provided market. - Inputs: - symbol(MARKET_SYMBOLS): the market symbol - Returns: - dict: all the contract addresses + Returns: + dict: all contract addresses for the all markets. """ - query = {"symbol": symbol.value } if symbol else {} - - return await self.apis.get( - SERVICE_URLS["MARKET"]["CONTRACT_ADDRESSES"], - query - ) + return await self.apis.get(SERVICE_URLS["MARKET"]["CONTRACT_ADDRESSES"]) ## User endpoints - + def get_account(self): """ Returns the user account object @@ -824,6 +776,7 @@ async def generate_readonly_token(self): {}, True ) + async def get_orders(self,params:GetOrderRequest): """ Returns a list of orders. @@ -928,7 +881,7 @@ async def get_cancel_on_disconnect_timer(self, params:GetCancelOnDisconnectTimer """ params = extract_enums(params, ["symbol"]) - response = await self.dmsApi.get( + response = await self.dms_api.get( SERVICE_URLS["USER"]["CANCEL_ON_DISCONNECT"], params, auth_required=True @@ -949,7 +902,7 @@ async def reset_cancel_on_disconnect_timer(self, params:PostTimerAttributes): - acceptedToReset: array with symbols for which timer was reset successfully - failedReset: aray with symbols for whcih timer failed to reset """ - response = await self.dmsApi.post( + response = await self.dms_api.post( SERVICE_URLS["USER"]["CANCEL_ON_DISCONNECT"], json.dumps(params), auth_required=True, @@ -960,25 +913,17 @@ async def reset_cancel_on_disconnect_timer(self, params:PostTimerAttributes): raise Exception("Cancel on Disconnect (dead-mans-switch) feature is currently unavailable") return response - ## Internal methods - def _get_order_signer(self,symbol:MARKET_SYMBOLS=None): - """ - Returns the order signer for the specified symbol, else returns a dictionary of symbol -> order signer - Inputs: - symbol(MARKET_SYMBOLS): the symbol to get order signer for, optional - Returns: - dict/order signer object - """ - if symbol: - if symbol.value in self.order_signers.keys(): - return self.order_signers[symbol.value] - else: - raise(Exception("Signer does not exist. Make sure to add market")) - else: - return self.order_signers - async def close_connections(self): # close aio http connection await self.apis.close_session() - await self.dmsApi.close_session() + await self.dms_api.close_session() + + + def _from_sui_base(self, number: Union[str,int]) -> float: + number=float(number) + return number/float(_SUI_BASE_NUM) + + + def _to_sui_base(self, number: Union[int,float]) -> int: + return int(number*_SUI_BASE_NUM) diff --git a/src/bluefin_client_sui/constants.py b/src/bluefin_client_sui/constants.py index 3f76621..fe91da3 100644 --- a/src/bluefin_client_sui/constants.py +++ b/src/bluefin_client_sui/constants.py @@ -5,8 +5,7 @@ "socketURL":"wss://dapi.api.sui-staging.bluefin.io", "dmsURL":"https://dapi.api.sui-staging.bluefin.io", "webSocketURL":"wss://notifications.api.sui-staging.bluefin.io", - "onboardingUrl": "https://testnet.bluefin.io", - + "onboardingUrl": "https://testnet.bluefin.io" }, "SUI_PROD":{ "url":"https://fullnode.testnet.sui.io:443", @@ -14,7 +13,7 @@ "socketURL":"wss://dapi.api.sui-prod.bluefin.io", "dmsURL":"https://dapi.api.sui-prod.bluefin.io", "webSocketURL":"wss://notifications.api.sui-prod.bluefin.io", - "onboardingUrl": "https://trade.bluefin.io", + "onboardingUrl": "https://trade.bluefin.io" } } @@ -68,6 +67,4 @@ }, } - -SUI_BASE_NUM=1000000000 DAPI_BASE_NUM=1000000000000000000 \ No newline at end of file diff --git a/src/bluefin_client_sui/contracts.py b/src/bluefin_client_sui/contracts.py index c59b3f9..118a33f 100644 --- a/src/bluefin_client_sui/contracts.py +++ b/src/bluefin_client_sui/contracts.py @@ -1,77 +1,34 @@ -from .enumerations import * +from .enumerations import MARKET_SYMBOLS + + +_DEFAULT_MARKET = "BTC-PERP" class Contracts: def __init__(self): - self.contracts = {} - self.contract_addresses = {} - - def set_contract_addresses(self,contract_address,market=None,name=None): - self.contract_addresses["auxiliaryContractsAddresses"]=contract_address['auxiliaryContractsAddresses'] - self.contract_addresses[market]=contract_address[market.value] + self.contracts_global_info = {} + self.contract_info = {} + + def set_contract_addresses(self,contracts_info): + self.contract_info = contracts_info + self.contracts_global_info = contracts_info[_DEFAULT_MARKET]['Deployment'] def get_sub_account_id(self): - return self.contract_addresses['auxiliaryContractsAddresses']['objects']['SubAccounts']['id'] + return self.contracts_global_info['SubAccounts']['id'] def get_bank_table_id(self): - return self.contract_addresses['auxiliaryContractsAddresses']['objects']['BankTable']['id'] + return self.contracts_global_info['BankTable']['id'] - def get_price_oracle_object_id(self, market): - return self.contract_addresses[market]['PriceOracle']['id'] + def get_package_id(self): + return self.contracts_global_info['package']['id'] - def get_perpetual_id(self, market: MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): - return self.contract_addresses[market]['Perpetual']['id'] - def get_package_id(self, market: MARKET_SYMBOLS=MARKET_SYMBOLS.ETH): - return self.contract_addresses[market]['package']['id'] - - def get_currency_type(self): - return self.contract_addresses['auxiliaryContractsAddresses']['objects']['Currency']['dataType'] - def get_bank_id(self): - return self.contract_addresses['auxiliaryContractsAddresses']['objects']['Bank']['id'] - def get_contract(self,name,market=""): - """ - Returns the contract object. - Inputs: - - name(str): The contract name. - - market(str): The market the contract belongs to (required for market specific contracts). - """ - try: - if name in self.contracts.keys(): - return self.contracts[name] - if market in self.contracts.keys() and name in self.contracts[market].keys(): - return self.contracts[market][name] - else: - return "Contract not found" - except Exception as e: - raise(Exception("Failed to get contract, Exception: {}".format(e))) - - def get_contract_address(self,name=None,market=None): - """ - Returns the contract address. If neither of the inputs provided, will return a dict with all contract addresses. - Inputs: - - name(str): The contract name. - - market(str): The market the contract belongs to (if only market provided will return all address of market as dict). - """ - try: - if market and name: - return self.contract_addresses[market][name] - elif market: - return self.contract_addresses[market] - elif name: - return self.contract_addresses["auxiliaryContractsAddresses"][name] - else: - return self.contract_addresses - except Exception as e: - raise(Exception("Failed to get contract address, Exception: {}".format(e))) - + return self.contracts_global_info['Bank']['id'] - - def get_market_id(self,market: str)-> str: - """ - Returns the market id/Perpetual ID for the respective market. - Inputs: - - market(str) the name of the market for which you need perp id e.g ETH-PERP - """ - return self.contract_addresses[market.value]['Perpetual']['id'] + def get_currency_type(self): + return self.contracts_global_info['Currency']['dataType'] + def get_price_oracle_object_id(self, market: MARKET_SYMBOLS): + return self.contract_info[market]['objects']['PriceOracle']['id'] - + def get_perpetual_id(self, market: MARKET_SYMBOLS): + return self.contract_info[market]['objects']['Perpetual']['id'] + \ No newline at end of file diff --git a/src/bluefin_client_sui/interfaces.py b/src/bluefin_client_sui/interfaces.py index 221f03c..50676ad 100644 --- a/src/bluefin_client_sui/interfaces.py +++ b/src/bluefin_client_sui/interfaces.py @@ -2,18 +2,18 @@ from .enumerations import * class Order(TypedDict): - market: str - price: int - isBuy: bool - reduceOnly: bool - quantity: int - postOnly: bool - orderbookOnly: bool - leverage: int - expiration: int - salt: int - maker: str - ioc: bool + market: str + price: int + isBuy: bool + reduceOnly: bool + quantity: int + postOnly: bool + orderbookOnly: bool + leverage: int + expiration: int + salt: int + maker: str + ioc: bool class SignedOrder(Order): @@ -217,7 +217,3 @@ class FailedCountDownResetResponse(TypedDict): class PostTimerResponse(TypedDict): acceptedToReset: List[str] failedReset: List[FailedCountDownResetResponse] - - - - diff --git a/src/bluefin_client_sui/order_signer.py b/src/bluefin_client_sui/order_signer.py index ac38a9f..59d821a 100644 --- a/src/bluefin_client_sui/order_signer.py +++ b/src/bluefin_client_sui/order_signer.py @@ -1,15 +1,12 @@ -from .utilities import bn_to_bytes8, address_to_bytes32,numberToHex, hexToByteArray -from .constants import * +from .utilities import numberToHex, hexToByteArray from .signer import Signer from .interfaces import Order import hashlib class OrderSigner(Signer): - def __init__(self, orders_contract_address="", domain="IsolatedTrader", version="1.0"): + def __init__(self, version="1.0"): super().__init__() - self.contract_address = orders_contract_address - self.domain = domain - self.version = version + self.version = version def get_order_flags(self, order): @@ -35,13 +32,6 @@ def get_order_flags(self, order): flag+=16 return flag - def get_domain_hash(self): - """ - Returns domain hash - """ - ### todo - return "" - def get_order_hash(self, order:Order, withBufferHex=True): """ @@ -65,18 +55,19 @@ def get_order_hash(self, order:Order, withBufferHex=True): bluefin=bytearray("Bluefin", encoding="utf-8") buffer=orderPriceHex+orderQuantityHex+orderLeverageHex+orderSalt+orderExpiration+orderMaker+orderMarket+flags+bluefin - + #for cancel order signature verification we use buffer directly # for placing order we use buffer.hex().encode("utf-8") if withBufferHex: order_hash=hashlib.sha256(buffer.hex().encode("utf-8")).digest() else: order_hash=hashlib.sha256(buffer).digest() - return order_hash + return order_hash + def sign_order(self, order:Order, private_key): """ Used to create an order signature. The method will use the provided key - in params(if any) to sign the order. + in params to sign the order. Args: order (Order): an order containing order fields (look at Order interface) @@ -87,23 +78,3 @@ def sign_order(self, order:Order, private_key): """ order_hash = self.get_order_hash(order) return self.sign_hash(order_hash, private_key, "") - - def sign_cancellation_hash(self,order_hash:list): - """ - Used to create a cancel order signature. The method will use the provided key - in params(if any) to sign the cancel order. - - Args: - order_hash(list): a list containing all orders to be cancelled - private_key (str): private key of the account to be used for signing - Returns: - str: generated signature - """ - sigDict={} - sigDict['orderHashes']=order_hash - encodedMessage=self.encode_message(sigDict) - return encodedMessage - - - - \ No newline at end of file diff --git a/src/bluefin_client_sui/utilities.py b/src/bluefin_client_sui/utilities.py index 10d2942..b9dd2d2 100644 --- a/src/bluefin_client_sui/utilities.py +++ b/src/bluefin_client_sui/utilities.py @@ -13,13 +13,6 @@ def toDapiBase(number: Union[int, float])->int: def fromDapiBase(number: Union[int, float], dtype=int)-> int: return dtype(number/DAPI_BASE_NUM) -def toSuiBase(number: Union[int,float]) -> int: - return int(number*SUI_BASE_NUM) - -def fromSuiBase(number: Union[str,int])-> float: - number=float(number) - return number/float(SUI_BASE_NUM) - def numberToHex(num, pad=32): #converting number to Hexadecimal format From 57a241a4722763de5dba1a4dbd36ba9efed8e979 Mon Sep 17 00:00:00 2001 From: Yasir Ejaz <105588724+yasir7ca@users.noreply.github.com> Date: Sun, 27 Aug 2023 16:53:26 +0400 Subject: [PATCH 10/12] Rebrand library to Bluefin instead of Firefly and format code using black formatter (#9) * format code and rebrand to bluefin * remove build directory * Add gitignore file * delete package info file * git p --- .gitignore | 1 + .vscode/settings.json | 6 + CHANGES.md | 2 +- README.md | 60 +- examples/1.initialization.py | 42 +- examples/10.1.socket_readonly.py | 135 ++- examples/10.sockets.py | 104 +- examples/11.sub_accounts.py | 69 +- examples/12.open_order_event.py | 60 +- examples/13.orderbook_updates.py | 52 +- examples/14.web_sockets.py | 79 +- examples/15.get_funding_history.py | 25 +- .../16.listening_events_using_sub_account.py | 105 +- examples/17.1.get_orders_readonly.py | 91 +- examples/17.get_orders.py | 86 +- examples/18.dms_api.py | 76 +- examples/19.Generate_readonly_token.py | 45 +- examples/2.user_info.py | 41 +- examples/20.contract_call.py | 69 +- examples/3.balance.py | 74 +- examples/4.placing_orders.py | 87 +- examples/5.adjusting_leverage.py | 52 +- examples/6.adjusting_margin.py | 93 +- examples/7.cancelling_orders.py | 64 +- examples/8.exchange_data.py | 37 +- examples/9.user_data.py | 59 +- examples/SocketClient.py | 13 +- examples/SocketServer.py | 24 +- examples/config.py | 8 +- examples/contract_call.py | 59 +- pyproject.toml | 2 +- src/bluefin_client_sui/__init__.py | 2 +- src/bluefin_client_sui/account.py | 44 +- src/bluefin_client_sui/api_service.py | 68 +- src/bluefin_client_sui/client.py | 1047 ++++++++--------- src/bluefin_client_sui/constants.py | 111 +- src/bluefin_client_sui/contracts.py | 25 +- src/bluefin_client_sui/enumerations.py | 43 +- src/bluefin_client_sui/interfaces.py | 322 ++--- src/bluefin_client_sui/onboarding_signer.py | 32 +- src/bluefin_client_sui/order_signer.py | 115 +- src/bluefin_client_sui/rpc.py | 89 +- src/bluefin_client_sui/signer.py | 59 +- src/bluefin_client_sui/socket_manager.py | 2 +- src/bluefin_client_sui/sockets_lib.py | 151 +-- src/bluefin_client_sui/utilities.py | 78 +- src/bluefin_client_sui/websocket_client.py | 138 ++- src/check.py | 42 +- 48 files changed, 2149 insertions(+), 1939 deletions(-) create mode 100644 .gitignore create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e2590f2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.formatOnSave": true + } +} \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 1599d90..94211d9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -38,7 +38,7 @@ 3. For signing onboarding signer we follow a different approach. - 1. What is onboarding signer: When we are calling a firefly init function we sign a string using our private key and send it to bluefin exchange along with our public key, bluefin exchange verifies it and returns us a token. + 1. What is onboarding signer: When we are calling a Bluefin init function we sign a string using our private key and send it to bluefin exchange along with our public key, bluefin exchange verifies it and returns us a token. 2. For signing it we first convert our message to bytes and then add [3,0,0, len(message)] to the start of our bytearray and then our message. if our message length is greater than 256 then it wont fit in a byte in this case we follow BCS methodology to send our message. 4. For signing cancel order, there are two ways. diff --git a/README.md b/README.md index 401249a..05ddaa5 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,11 @@ -# Firefly Client Library - -[Firefly logo](#) -
+ -![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/fireflyprotocol/bluefin-client-python-sui/publish_to_pypi.yml) -[![pypi version](https://img.shields.io/pypi/v/bluefun_client_sui?logo=pypi)](https://pypi.org/project/bluefin_client_sui/) -[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +

Bluefin Python Client Library

-Python Client for the Firefly Exchange API and Smart Contracts for SUI. +Python Client for the Bluefin Exchange API and SUI Contracts. ​ ### Install @@ -27,11 +22,11 @@ Alternatively, you could run: pip install . ``` -The package currently supports python `>=3.8`. Find complete documentation on the library at https://docs.firefly.exchange/. +The package currently supports python `>=3.8`. Find complete documentation on the library at https://docs.bluefin.io/. ### Getting Started -When initializing the client, users must accept [terms and conditions](https://firefly.exchange/terms-of-use) and define network object containing the following values: +When initializing the client, users must accept [terms and conditions](https://bluefin.io/terms-of-use) and define network object containing the following values: ```json { @@ -49,31 +44,30 @@ Users can import predefined networks from [constants](https://github.com/firefly from bluefin_client_sui import Networks ``` -For testing purposes use `Networks[SUI_STAGING]` and for production please use `Networks[SUI_PROD]`. +For testing purposes use `Networks[SUI_STAGING]` and for production use `Networks[SUI_PROD]`. ## Initialization example​ ```python from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks +from bluefin_client_sui import BluefinClient, Networks from pprint import pprint import asyncio async def main(): # initialize client - client = FireflyClient( + client = BluefinClient( True, # agree to terms and conditions Networks[TEST_NETWORK], # network to connect with TEST_ACCT_KEY, # private key of wallet - ) + ) - # initialize the client - # on boards user on firefly. Must be set to true for first time use + # on boards user on bluefin. Must be set to true for firs time use await client.init(True) - print('Account Address:', client.get_public_address()) + print('Account address:', client.get_public_address()) - # # gets user account data on-chain + # gets user account on-chain data data = await client.get_user_account_data() # close aio http connection @@ -88,20 +82,20 @@ if __name__ == "__main__": ``` **Read-only Initialization:** -Firefly-client can also be initialized in `read-only` mode, below is the example: +Bluefin-client can also be initialized in `read-only` mode, below is the example: ```python from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks +from bluefin_client_sui import BluefinClient, Networks from pprint import pprint import asyncio async def main(): # initialize client without providing private_key - client = FireflyClient( + client = BluefinClient( True, # agree to terms and conditions Networks[TEST_NETWORK], # network to connect with - ) + ) # Initializing client for the private key provided. The second argument api_token is optional await client.init(True,"54b0bfafc9a48728f76e52848a716e96d490263392e3959c2d44f05dea960761") @@ -124,16 +118,16 @@ if __name__ == "__main__": ```python from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +from bluefin_client_sui import BluefinClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest import asyncio async def main(): # initialize client - client = FireflyClient( + client = BluefinClient( True, # agree to terms and conditions Networks[TEST_NETWORK], # network to connect with TEST_ACCT_KEY, # private key of wallet - ) + ) await client.init(True) @@ -173,7 +167,7 @@ if __name__ == "__main__": ```python from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, SOCKET_EVENTS +from bluefin_client_sui import BluefinClient, Networks, SOCKET_EVENTS import asyncio import time @@ -182,13 +176,13 @@ def callback(event): async def main(): # initialize - client = FireflyClient( + client = BluefinClient( True, # agree to terms and conditions Networks[TEST_NETWORK], # network to connect with TEST_ACCT_KEY, # private key of wallet ) await client.init(True) - # make connection with firefly exchange + # make connection with bluefin exchange await client.socket.open() # subscribe to local user events @@ -208,13 +202,13 @@ if __name__ == "__main__": loop.close()​ ``` -Look at the [example](https://github.com/fireflyprotocol/firefly_exchange_client/tree/main/examples) directory to see more examples on how to use this library. +Look at the [example](https://github.com/fireflyprotocol/bluefin-client-python-sui/tree/main/examples) directory to see more examples on how to use this library. **Listening To Events Using Web Sockets:** ```python from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, SOCKET_EVENTS, MARKET_SYMBOLS +from bluefin_client_sui import BluefinClient, Networks, SOCKET_EVENTS, MARKET_SYMBOLS import time import asyncio @@ -223,11 +217,11 @@ def callback(event): async def main(): # initialize - client = FireflyClient( + client = BluefinClient( True, # agree to terms and conditions Networks[TEST_NETWORK], # network to connect with TEST_ACCT_KEY, # private key of wallet - ) + ) await client.init(True) @@ -240,7 +234,7 @@ async def main(): if resp: print("Subscribed to user updates") - # make connection with firefly exchange + # make connection with bluefin exchange client.webSocketClient.initialize_socket(on_open=on_open) # listen to user order updates and trigger callback client.webSocketClient.listen(SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback) diff --git a/examples/1.initialization.py b/examples/1.initialization.py index b37edc0..ebf65d3 100644 --- a/examples/1.initialization.py +++ b/examples/1.initialization.py @@ -1,36 +1,38 @@ import os import sys -print (os.getcwd()) -sys.path.append(os.getcwd()+"/src/") +print(os.getcwd()) +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks +from bluefin_client_sui import BluefinClient, Networks from pprint import pprint import asyncio + async def main(): - # initialize client - client = FireflyClient( - True, # agree to terms and conditions - Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY, # private key of wallet - ) + # initialize client + client = BluefinClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + # Initializing client for the private key provided. The second argument api_token is optional + await client.init(True) + + print("Account Address:", client.get_public_address()) - # Initializing client for the private key provided. The second argument api_token is optional - await client.init(True) - - print('Account Address:', client.get_public_address()) + # # gets user account data on-chain + data = await client.get_user_account_data() - # # gets user account data on-chain - data = await client.get_user_account_data() + await client.close_connections() - await client.close_connections() + pprint(data) - pprint(data) if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/10.1.socket_readonly.py b/examples/10.1.socket_readonly.py index 3c27096..ea6cd88 100644 --- a/examples/10.1.socket_readonly.py +++ b/examples/10.1.socket_readonly.py @@ -1,15 +1,17 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") import time from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS +from bluefin_client_sui import BluefinClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS import asyncio -TEST_NETWORK="SUI_STAGING" +TEST_NETWORK = "SUI_STAGING" event_received = False + def callback(event): global event_received print("Event data:", event) @@ -17,70 +19,67 @@ def callback(event): async def main(): - - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) - await client.init(True) - response = await client.generate_readonly_token() - readOnlyclient = FireflyClient(True, Networks[TEST_NETWORK]) - await readOnlyclient.init(True,response) - - - async def my_callback(): - print("Subscribing To Rooms") - # subscribe to global event updates for BTC market - status = await readOnlyclient.socket.subscribe_global_updates_by_symbol(MARKET_SYMBOLS.BTC) - print("Subscribed to global BTC events: {}".format(status)) - - # subscribe to local user events - status = await readOnlyclient.socket.subscribe_user_update_by_token() - print("Subscribed to user events: {}".format(status)) - - # triggered when order book updates - print("Listening to exchange health updates") - await readOnlyclient.socket.listen(SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback) - - # triggered when status of any user order updates - print("Listening to user order updates") - await readOnlyclient.socket.listen(SOCKET_EVENTS.ORDER_UPDATE.value, callback) - - - - await readOnlyclient.socket.listen("connect",my_callback) - - - - # must open socket before subscribing - print("Making socket connection to firefly exchange") - await readOnlyclient.socket.open() - - - # SOCKET_EVENTS contains all events that can be listened to - - # logs event name and data for all markets and users that are subscribed. - # helpful for debugging - # client.socket.listen("default",callback) - timeout = 30 - end_time = time.time() + timeout - while not event_received and time.time() < end_time: - time.sleep(1) - - # # unsubscribe from global events - status = await readOnlyclient.socket.unsubscribe_global_updates_by_symbol(MARKET_SYMBOLS.BTC) - print("Unsubscribed from global BTC events: {}".format(status)) - - status = await readOnlyclient.socket.unsubscribe_user_update_by_token() - print("Unsubscribed from user events: {}".format(status)) - - - # # close socket connection - print("Closing sockets!") - await readOnlyclient.socket.close() - - await readOnlyclient.apis.close_session() - + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + response = await client.generate_readonly_token() + readOnlyclient = BluefinClient(True, Networks[TEST_NETWORK]) + await readOnlyclient.init(True, response) + + async def my_callback(): + print("Subscribing To Rooms") + # subscribe to global event updates for BTC market + status = await readOnlyclient.socket.subscribe_global_updates_by_symbol( + MARKET_SYMBOLS.BTC + ) + print("Subscribed to global BTC events: {}".format(status)) + + # subscribe to local user events + status = await readOnlyclient.socket.subscribe_user_update_by_token() + print("Subscribed to user events: {}".format(status)) + + # triggered when order book updates + print("Listening to exchange health updates") + await readOnlyclient.socket.listen( + SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback + ) + + # triggered when status of any user order updates + print("Listening to user order updates") + await readOnlyclient.socket.listen(SOCKET_EVENTS.ORDER_UPDATE.value, callback) + + await readOnlyclient.socket.listen("connect", my_callback) + + # must open socket before subscribing + print("Making socket connection to Bluefin exchange") + await readOnlyclient.socket.open() + + # SOCKET_EVENTS contains all events that can be listened to + + # logs event name and data for all markets and users that are subscribed. + # helpful for debugging + # client.socket.listen("default",callback) + timeout = 30 + end_time = time.time() + timeout + while not event_received and time.time() < end_time: + time.sleep(1) + + # # unsubscribe from global events + status = await readOnlyclient.socket.unsubscribe_global_updates_by_symbol( + MARKET_SYMBOLS.BTC + ) + print("Unsubscribed from global BTC events: {}".format(status)) + + status = await readOnlyclient.socket.unsubscribe_user_update_by_token() + print("Unsubscribed from user events: {}".format(status)) + + # # close socket connection + print("Closing sockets!") + await readOnlyclient.socket.close() + + await readOnlyclient.apis.close_session() if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/10.sockets.py b/examples/10.sockets.py index 6ccfb1e..eb855a0 100644 --- a/examples/10.sockets.py +++ b/examples/10.sockets.py @@ -1,13 +1,16 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") import time from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS +from bluefin_client_sui import BluefinClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS import asyncio -TEST_NETWORK="SUI_STAGING" + +TEST_NETWORK = "SUI_STAGING" event_received = False + def callback(event): global event_received print("Event data:", event) @@ -15,67 +18,62 @@ def callback(event): async def main(): + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) - await client.init(True) - - - async def my_callback(): - print("Subscribing To Rooms") - # subscribe to global event updates for BTC market - status = await client.socket.subscribe_global_updates_by_symbol(MARKET_SYMBOLS.BTC) - print("Subscribed to global BTC events: {}".format(status)) - - # subscribe to local user events - status = await client.socket.subscribe_user_update_by_token() - print("Subscribed to user events: {}".format(status)) - - # triggered when order book updates - print("Listening to exchange health updates") - await client.socket.listen(SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback) + async def my_callback(): + print("Subscribing To Rooms") + # subscribe to global event updates for BTC market + status = await client.socket.subscribe_global_updates_by_symbol( + MARKET_SYMBOLS.BTC + ) + print("Subscribed to global BTC events: {}".format(status)) - # triggered when status of any user order updates - print("Listening to user order updates") - await client.socket.listen(SOCKET_EVENTS.ORDER_UPDATE.value, callback) + # subscribe to local user events + status = await client.socket.subscribe_user_update_by_token() + print("Subscribed to user events: {}".format(status)) + # triggered when order book updates + print("Listening to exchange health updates") + await client.socket.listen(SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback) - - await client.socket.listen("connect",my_callback) - - + # triggered when status of any user order updates + print("Listening to user order updates") + await client.socket.listen(SOCKET_EVENTS.ORDER_UPDATE.value, callback) - # must open socket before subscribing - print("Making socket connection to firefly exchange") - await client.socket.open() + await client.socket.listen("connect", my_callback) - - # SOCKET_EVENTS contains all events that can be listened to - - # logs event name and data for all markets and users that are subscribed. - # helpful for debugging - # client.socket.listen("default",callback) - timeout = 30 - end_time = time.time() + timeout - while not event_received and time.time() < end_time: - time.sleep(1) + # must open socket before subscribing + print("Making socket connection to Bluefin exchange") + await client.socket.open() - # # unsubscribe from global events - status = await client.socket.unsubscribe_global_updates_by_symbol(MARKET_SYMBOLS.BTC) - print("Unsubscribed from global BTC events: {}".format(status)) + # SOCKET_EVENTS contains all events that can be listened to - status = await client.socket.unsubscribe_user_update_by_token() - print("Unsubscribed from user events: {}".format(status)) + # logs event name and data for all markets and users that are subscribed. + # helpful for debugging + # client.socket.listen("default",callback) + timeout = 30 + end_time = time.time() + timeout + while not event_received and time.time() < end_time: + time.sleep(1) + # # unsubscribe from global events + status = await client.socket.unsubscribe_global_updates_by_symbol( + MARKET_SYMBOLS.BTC + ) + print("Unsubscribed from global BTC events: {}".format(status)) - # # close socket connection - print("Closing sockets!") - await client.socket.close() + status = await client.socket.unsubscribe_user_update_by_token() + print("Unsubscribed from user events: {}".format(status)) - await client.apis.close_session() + # # close socket connection + print("Closing sockets!") + await client.socket.close() + await client.apis.close_session() if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/11.sub_accounts.py b/examples/11.sub_accounts.py index 7fb68e9..a7b11b0 100644 --- a/examples/11.sub_accounts.py +++ b/examples/11.sub_accounts.py @@ -1,52 +1,61 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_SUB_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, Networks, OrderSignatureRequest +from bluefin_client_sui import ( + BluefinClient, + MARKET_SYMBOLS, + ORDER_SIDE, + ORDER_TYPE, + Networks, + OrderSignatureRequest, +) import asyncio from bluefin_client_sui.utilities import * async def main(): + clientParent = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await clientParent.init(True) - clientParent = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) - await clientParent.init(True) - - clientChild = FireflyClient(True, Networks[TEST_NETWORK], TEST_SUB_ACCT_KEY) - await clientChild.init(True) + clientChild = BluefinClient(True, Networks[TEST_NETWORK], TEST_SUB_ACCT_KEY) + await clientChild.init(True) - print("Parent: ", clientParent.get_public_address()) + print("Parent: ", clientParent.get_public_address()) - print("Child: ", clientChild.get_public_address()) + print("Child: ", clientChild.get_public_address()) - # # whitelist sub account - status = await clientParent.update_sub_account(clientChild.get_public_address(), True) - print(f"Sub account created: {status}") + # # whitelist sub account + status = await clientParent.update_sub_account( + clientChild.get_public_address(), True + ) + print(f"Sub account created: {status}") - parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) - await clientParent.adjust_leverage(MARKET_SYMBOLS.ETH,1) - parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) - signature_request = OrderSignatureRequest( - symbol=MARKET_SYMBOLS.ETH, # sub account is only whitelisted for ETH market + parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) + await clientParent.adjust_leverage(MARKET_SYMBOLS.ETH, 1) + parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # sub account is only whitelisted for ETH market maker=clientParent.get_public_address(), # maker of the order is the parent account - price=0, + price=0, quantity=0.02, - side=ORDER_SIDE.BUY, + side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.MARKET, leverage=parent_leverage, - ) + ) - # order is signed using sub account's private key - signed_order = clientChild.create_signed_order(signature_request) + # order is signed using sub account's private key + signed_order = clientChild.create_signed_order(signature_request) - resp = await clientChild.post_signed_order(signed_order) + resp = await clientChild.post_signed_order(signed_order) - print(resp) + print(resp) - await clientChild.close_connections() - await clientParent.close_connections() + await clientChild.close_connections() + await clientParent.close_connections() if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/12.open_order_event.py b/examples/12.open_order_event.py index 8b94122..a65c53f 100644 --- a/examples/12.open_order_event.py +++ b/examples/12.open_order_event.py @@ -1,40 +1,46 @@ -''' +""" The code example opens socket connection and listens to user order update events It places a limit order and as soon as its OPENED on order book, we receive an event, log its data and terminate connection -''' -import sys,os -sys.path.append(os.getcwd()+"/src/") +""" +import sys, os + +sys.path.append(os.getcwd() + "/src/") import time from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + SOCKET_EVENTS, + ORDER_SIDE, + ORDER_TYPE, + OrderSignatureRequest, +) import asyncio -TEST_NETWORK="SUI_STAGING" - -event_received = False +TEST_NETWORK = "SUI_STAGING" +event_received = False -async def place_limit_order(client:FireflyClient): - - # default leverage of account is set to 3 on firefly - await client.adjust_leverage(MARKET_SYMBOLS.ETH,1) +async def place_limit_order(client: BluefinClient): + # default leverage of account is set to 3 on Bluefin + await client.adjust_leverage(MARKET_SYMBOLS.ETH, 1) user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) - # creates a LIMIT order to be signed signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol price=1300000000000, # price at which you want to place order - quantity=10000000, # quantity - side=ORDER_SIDE.SELL, + quantity=10000000, # quantity + side=ORDER_SIDE.SELL, orderType=ORDER_TYPE.LIMIT, - leverage=1000000000 - ) + leverage=1000000000, + ) # create signed order - signed_order = client.create_signed_order(signature_request) + signed_order = client.create_signed_order(signature_request) print("Placing a limit order") # place signed order on orderbook @@ -47,8 +53,7 @@ async def place_limit_order(client:FireflyClient): async def main(): - - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) def callback(event): @@ -56,18 +61,17 @@ def callback(event): print("Event data:", event) event_received = True - # must open socket before subscribing - print("Making socket connection to firefly exchange") + print("Making socket connection to Bluefin exchange") await client.socket.open() - # subscribe to user events + # subscribe to user events await client.socket.subscribe_user_update_by_token() print("Subscribed to user events") print("Listening to user order updates") await client.socket.listen(SOCKET_EVENTS.ORDER_UPDATE.value, callback) - + # place a limit order await place_limit_order(client) @@ -86,9 +90,7 @@ def callback(event): await client.close_connections() - if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() - + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/13.orderbook_updates.py b/examples/13.orderbook_updates.py index c3ff327..49531be 100644 --- a/examples/13.orderbook_updates.py +++ b/examples/13.orderbook_updates.py @@ -1,32 +1,43 @@ -''' +""" When ever the state of orderbook changes, an event is emitted by exchange. In this code example we open a socket connection and listen to orderbook update event -''' -import sys,os -sys.path.append(os.getcwd()+"/src/") +""" +import sys, os + +sys.path.append(os.getcwd() + "/src/") import time from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + SOCKET_EVENTS, + ORDER_SIDE, + ORDER_TYPE, + OrderSignatureRequest, +) import asyncio -TEST_NETWORK="SUI_STAGING" + +TEST_NETWORK = "SUI_STAGING" event_received = False -async def place_limit_order(client:FireflyClient): - # default leverage of account is set to 3 on firefly + +async def place_limit_order(client: BluefinClient): + # default leverage of account is set to 3 on Bluefin user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) # creates a LIMIT order to be signed signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol price=1300, # price at which you want to place order - quantity=0.01, # quantity + quantity=0.01, # quantity side=ORDER_SIDE.SELL, orderType=ORDER_TYPE.LIMIT, - leverage=user_leverage + leverage=user_leverage, ) # create signed order - signed_order = client.create_signed_order(signature_request) + signed_order = client.create_signed_order(signature_request) print("Placing a limit order") # place signed order on orderbook @@ -36,35 +47,36 @@ async def place_limit_order(client:FireflyClient): print(resp) return -async def main(): - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) +async def main(): + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - + def callback(event): global event_received print("Event data:", event) event_received = True - # must open socket before subscribing - print("Making socket connection to firefly exchange") + print("Making socket connection to Bluefin exchange") await client.socket.open() - # subscribe to global event updates for ETH market + # subscribe to global event updates for ETH market await client.socket.subscribe_global_updates_by_symbol(MARKET_SYMBOLS.ETH) print("Subscribed to ETH Market events") print("Listening to ETH Orderbook update event") await client.socket.listen(SOCKET_EVENTS.ORDERBOOK_UPDATE.value, callback) - await place_limit_order(client) - + await place_limit_order(client) + timeout = 30 end_time = time.time() + timeout while not event_received and time.time() < end_time: time.sleep(1) - status = await client.socket.unsubscribe_global_updates_by_symbol(MARKET_SYMBOLS.ETH) + status = await client.socket.unsubscribe_global_updates_by_symbol( + MARKET_SYMBOLS.ETH + ) print("Unsubscribed from orderbook update events for ETH Market: {}".format(status)) # close socket connection diff --git a/examples/14.web_sockets.py b/examples/14.web_sockets.py index de2060b..50885ce 100644 --- a/examples/14.web_sockets.py +++ b/examples/14.web_sockets.py @@ -1,9 +1,15 @@ +import sys, os -import sys,os -sys.path.append(os.getcwd()+"/src/") +sys.path.append(os.getcwd() + "/src/") import time from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, SOCKET_EVENTS, config_logging +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + SOCKET_EVENTS, + config_logging, +) import asyncio import logging @@ -11,52 +17,59 @@ event_received = False + def callback(event): - global event_received - print("Event data:", event) - event_received = True + global event_received + print("Event data:", event) + event_received = True + async def main(): - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) - await client.init(True) - - def on_error(ws, error): + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + def on_error(ws, error): print(error) - - def on_close(ws): + + def on_close(ws): # unsubscribe from global events - status = client.webSocketClient.unsubscribe_global_updates_by_symbol(MARKET_SYMBOLS.ETH) + status = client.webSocketClient.unsubscribe_global_updates_by_symbol( + MARKET_SYMBOLS.ETH + ) print("Unsubscribed from global ETH events: {}".format(status)) # close socket connection print("### closed ###") - - def on_open(ws): - # subscribe to global event updates for ETH market - status = client.webSocketClient.subscribe_global_updates_by_symbol(MARKET_SYMBOLS.ETH) + + def on_open(ws): + # subscribe to global event updates for ETH market + status = client.webSocketClient.subscribe_global_updates_by_symbol( + MARKET_SYMBOLS.ETH + ) print("Subscribed to global ETH events: {}".format(status)) # SOCKET_EVENTS contains all events that can be listened to print("Listening to Exchange Health updates") client.webSocketClient.listen(SOCKET_EVENTS.EXCHANGE_HEALTH.value, callback) - # logs event name and data for all markets and users that are subscribed. # helpful for debugging # client.socket.listen("default",callback) - - - print("Making socket connection to firefly exchange") - client.webSocketClient.initialize_socket(on_open=on_open, on_error=on_error,on_close=on_close) - - timeout = 30 - end_time = time.time() + timeout - while not event_received and time.time() < end_time: - time.sleep(1) - - client.webSocketClient.stop() - await client.close_connections() + + print("Making socket connection to Bluefin exchange") + client.webSocketClient.initialize_socket( + on_open=on_open, on_error=on_error, on_close=on_close + ) + + timeout = 30 + end_time = time.time() + timeout + while not event_received and time.time() < end_time: + time.sleep(1) + + client.webSocketClient.stop() + await client.close_connections() + if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/15.get_funding_history.py b/examples/15.get_funding_history.py index 5e41bb6..dd45644 100644 --- a/examples/15.get_funding_history.py +++ b/examples/15.get_funding_history.py @@ -1,21 +1,26 @@ +import sys, os -import sys,os -sys.path.append(os.getcwd()+"/src/") +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, GetFundingHistoryRequest +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + GetFundingHistoryRequest, +) from pprint import pprint import asyncio + async def main(): - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - # create a funding history request funding_history_request = GetFundingHistoryRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol - pageSize=50, # gets provided number of payments <= 50 - cursor=0 # fetch a particular page. A single page contains upto 50 records + pageSize=50, # gets provided number of payments <= 50 + cursor=0, # fetch a particular page. A single page contains upto 50 records ) # submit request for funding history @@ -28,6 +33,6 @@ async def main(): if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/16.listening_events_using_sub_account.py b/examples/16.listening_events_using_sub_account.py index 8be39ed..c13ceba 100644 --- a/examples/16.listening_events_using_sub_account.py +++ b/examples/16.listening_events_using_sub_account.py @@ -1,80 +1,93 @@ -''' +""" This example shows how you can listen to user events using sub account On our exchange, a sub account is trading on its parent's position and thus has no position of its own. So when placing orders or listening to position updates the sub account must specify the parent address whose position its listening. -''' +""" import time, sys from config import TEST_ACCT_KEY, TEST_NETWORK, TEST_SUB_ACCT_KEY -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, OrderSignatureRequest, ORDER_SIDE, ORDER_TYPE, SOCKET_EVENTS +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + OrderSignatureRequest, + ORDER_SIDE, + ORDER_TYPE, + SOCKET_EVENTS, +) import asyncio + def callback(event): print("Event data: {}\n".format(event)) -async def main(): - - clientParent = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) - await clientParent.init(True) - clientChild = FireflyClient(True, Networks[TEST_NETWORK], TEST_SUB_ACCT_KEY) - await clientChild.init(True) - - print("Parent: ", clientParent.get_public_address()) +async def main(): + clientParent = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await clientParent.init(True) - print("Child: ", clientChild.get_public_address()) + clientChild = BluefinClient(True, Networks[TEST_NETWORK], TEST_SUB_ACCT_KEY) + await clientChild.init(True) - # # whitelist sub account - status = await clientParent.update_sub_account(MARKET_SYMBOLS.ETH, clientChild.get_public_address(), True) - print("Sub account created: {}\n".format(status)) + print("Parent: ", clientParent.get_public_address()) + print("Child: ", clientChild.get_public_address()) - # must open socket before subscribing - print("Making socket connection to firefly exchange") - await clientChild.socket.open() + # # whitelist sub account + status = await clientParent.update_sub_account( + MARKET_SYMBOLS.ETH, clientChild.get_public_address(), True + ) + print("Sub account created: {}\n".format(status)) - # subscribe to parent's events - resp = await clientChild.socket.subscribe_user_update_by_token(clientParent.get_public_address()) - print("Subscribed to parent's events:",resp) + # must open socket before subscribing + print("Making socket connection to Bluefin exchange") + await clientChild.socket.open() + # subscribe to parent's events + resp = await clientChild.socket.subscribe_user_update_by_token( + clientParent.get_public_address() + ) + print("Subscribed to parent's events:", resp) - # triggered when status of any user order updates - print("Listening to parents position updates") - await clientChild.socket.listen(SOCKET_EVENTS.POSITION_UPDATE.value, callback) + # triggered when status of any user order updates + print("Listening to parents position updates") + await clientChild.socket.listen(SOCKET_EVENTS.POSITION_UPDATE.value, callback) - parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) + parent_leverage = await clientParent.get_user_leverage(MARKET_SYMBOLS.ETH) - signature_request = OrderSignatureRequest( - symbol=MARKET_SYMBOLS.ETH, # sub account is only whitelisted for ETH market + signature_request = OrderSignatureRequest( + symbol=MARKET_SYMBOLS.ETH, # sub account is only whitelisted for ETH market maker=clientParent.get_public_address(), # maker of the order is the parent account - price=0, + price=0, quantity=0.02, - side=ORDER_SIDE.BUY, + side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.MARKET, leverage=parent_leverage, - ) + ) + + # order is signed using sub account's private key + signed_order = clientChild.create_signed_order(signature_request) - # order is signed using sub account's private key - signed_order = clientChild.create_signed_order(signature_request) + resp = await clientChild.post_signed_order(signed_order) - resp = await clientChild.post_signed_order(signed_order) + print(resp) - print(resp) + time.sleep(10) - time.sleep(10) + status = await clientChild.socket.unsubscribe_user_update_by_token( + clientParent.get_public_address() + ) + print("Unsubscribed from user events: {}".format(status)) - status = await clientChild.socket.unsubscribe_user_update_by_token(clientParent.get_public_address()) - print("Unsubscribed from user events: {}".format(status)) + # close socket connection + print("Closing sockets!") + await clientChild.socket.close() - # close socket connection - print("Closing sockets!") - await clientChild.socket.close() - - await clientChild.close_connections() - await clientParent.close_connections() + await clientChild.close_connections() + await clientParent.close_connections() if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/17.1.get_orders_readonly.py b/examples/17.1.get_orders_readonly.py index 7824502..38a1b4e 100644 --- a/examples/17.1.get_orders_readonly.py +++ b/examples/17.1.get_orders_readonly.py @@ -1,69 +1,76 @@ -''' +""" This example shows how users can get their orders information. The get_orders route provides a number of optional params that can be mixed together to fetch the exact data that user needs. -''' +""" -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_STATUS, ORDER_TYPE +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + ORDER_STATUS, + ORDER_TYPE, +) import asyncio async def main(): - - client = FireflyClient(True, Networks[TEST_NETWORK],TEST_ACCT_KEY) + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) response = await client.generate_readonly_token() - readOnlyclient = FireflyClient(True, Networks[TEST_NETWORK]) - await readOnlyclient.init(True,response) - - - + readOnlyclient = BluefinClient(True, Networks[TEST_NETWORK]) + await readOnlyclient.init(True, response) print("Get all ETH market orders regardless of their type/status") - orders = await readOnlyclient.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - }) - print('Received orders: ', len(orders)) + orders = await readOnlyclient.get_orders( + { + "symbol": MARKET_SYMBOLS.ETH, + } + ) + print("Received orders: ", len(orders)) print("Get orders based on status (OPEN and PENDING)") - orders = await readOnlyclient.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - "statuses": [ORDER_STATUS.OPEN, ORDER_STATUS.PENDING] - }) - print('Received orders: ', len(orders)) - + orders = await readOnlyclient.get_orders( + { + "symbol": MARKET_SYMBOLS.ETH, + "statuses": [ORDER_STATUS.OPEN, ORDER_STATUS.PENDING], + } + ) + print("Received orders: ", len(orders)) print("Get an order 180318 using id (possible this order is not available anymore)") - orders = await readOnlyclient.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - "orderId": 180318 - }) - print('Received orders: ', len(orders)) + orders = await readOnlyclient.get_orders( + {"symbol": MARKET_SYMBOLS.ETH, "orderId": 180318} + ) + print("Received orders: ", len(orders)) print("Get orders using hashes (possible these orders are not available anymore)") - orders = await readOnlyclient.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - "orderHashes": [ - "0x21eeb24b0af6832989484e61294db70e8cf8ce0e030c6cfbbb23f3b3d85f9374", - "0xd61fe390f6e6d89a884c73927741ba7d2d024e01f65af61f13363403e805e2c0"] - }) - print('Received orders: ', len(orders)) + orders = await readOnlyclient.get_orders( + { + "symbol": MARKET_SYMBOLS.ETH, + "orderHashes": [ + "0x21eeb24b0af6832989484e61294db70e8cf8ce0e030c6cfbbb23f3b3d85f9374", + "0xd61fe390f6e6d89a884c73927741ba7d2d024e01f65af61f13363403e805e2c0", + ], + } + ) + print("Received orders: ", len(orders)) print("Get only MARKET orders for ETH market") - orders = await readOnlyclient.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - "orderType": [ORDER_TYPE.MARKET] - }) - print('Received orders: ', len(orders)) + orders = await readOnlyclient.get_orders( + {"symbol": MARKET_SYMBOLS.ETH, "orderType": [ORDER_TYPE.MARKET]} + ) + print("Received orders: ", len(orders)) await readOnlyclient.close_connections() if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/17.get_orders.py b/examples/17.get_orders.py index c4a80af..a97a82c 100644 --- a/examples/17.get_orders.py +++ b/examples/17.get_orders.py @@ -1,61 +1,69 @@ -''' +""" This example shows how users can get their orders information. The get_orders route provides a number of optional params that can be mixed together to fetch the exact data that user needs. -''' -import sys,os -sys.path.append(os.getcwd()+"/src/") +""" +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_STATUS, ORDER_TYPE +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + ORDER_STATUS, + ORDER_TYPE, +) import asyncio -async def main(): - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) +async def main(): + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - print("Get all ETH market orders regardless of their type/status") - orders = await client.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - }) - print('Received orders: ', len(orders)) + orders = await client.get_orders( + { + "symbol": MARKET_SYMBOLS.ETH, + } + ) + print("Received orders: ", len(orders)) print("Get orders based on status (OPEN and PENDING)") - orders = await client.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - "statuses": [ORDER_STATUS.OPEN, ORDER_STATUS.PENDING] - }) - print('Received orders: ', len(orders)) - + orders = await client.get_orders( + { + "symbol": MARKET_SYMBOLS.ETH, + "statuses": [ORDER_STATUS.OPEN, ORDER_STATUS.PENDING], + } + ) + print("Received orders: ", len(orders)) print("Get an order 180318 using id (possible this order is not available anymore)") - orders = await client.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - "orderId": 180318 - }) - print('Received orders: ', len(orders)) + orders = await client.get_orders({"symbol": MARKET_SYMBOLS.ETH, "orderId": 180318}) + print("Received orders: ", len(orders)) print("Get orders using hashes (possible these orders are not available anymore)") - orders = await client.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - "orderHashes": [ - "0x21eeb24b0af6832989484e61294db70e8cf8ce0e030c6cfbbb23f3b3d85f9374", - "0xd61fe390f6e6d89a884c73927741ba7d2d024e01f65af61f13363403e805e2c0"] - }) - print('Received orders: ', len(orders)) + orders = await client.get_orders( + { + "symbol": MARKET_SYMBOLS.ETH, + "orderHashes": [ + "0x21eeb24b0af6832989484e61294db70e8cf8ce0e030c6cfbbb23f3b3d85f9374", + "0xd61fe390f6e6d89a884c73927741ba7d2d024e01f65af61f13363403e805e2c0", + ], + } + ) + print("Received orders: ", len(orders)) print("Get only MARKET orders for ETH market") - orders = await client.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - "orderType": [ORDER_TYPE.MARKET] - }) - print('Received orders: ', len(orders)) + orders = await client.get_orders( + {"symbol": MARKET_SYMBOLS.ETH, "orderType": [ORDER_TYPE.MARKET]} + ) + print("Received orders: ", len(orders)) - await client.close_connections() + await client.close_connections() if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/18.dms_api.py b/examples/18.dms_api.py index cd28326..ab6a95a 100644 --- a/examples/18.dms_api.py +++ b/examples/18.dms_api.py @@ -1,52 +1,50 @@ import json -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_SUB_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, Networks, OrderSignatureRequest +from bluefin_client_sui import ( + BluefinClient, + MARKET_SYMBOLS, + ORDER_SIDE, + ORDER_TYPE, + Networks, + OrderSignatureRequest, +) import asyncio from bluefin_client_sui.interfaces import PostTimerAttributes - async def main(): + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + await client.init(True) + + print("User: ", client.get_public_address()) + countDownsObject: PostTimerAttributes = dict() + countDowns = list() + countDowns.append({"symbol": MARKET_SYMBOLS.BTC.value, "countDown": 3 * 1000}) + + countDowns.append({"symbol": MARKET_SYMBOLS.ETH.value, "countDown": 3 * 1000}) + + countDownsObject["countDowns"] = countDowns + try: + # sending post request to reset user's count down timer with MARKET_SYMBOL for auto cancellation of order + postResponse = await client.reset_cancel_on_disconnect_timer(countDownsObject) + print(postResponse) + # get request to get user's count down timer for MARKET_SYMBOL + getResponse = await client.get_cancel_on_disconnect_timer( + {"symbol": MARKET_SYMBOLS.ETH} + ) + print(getResponse) + + except Exception as e: + print(e) - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) - await client.init(True) - - print("User: ", client.get_public_address()) - countDownsObject: PostTimerAttributes = dict() - countDowns = list() - countDowns.append({ - 'symbol': MARKET_SYMBOLS.BTC.value, - 'countDown': 3 * 1000 - } - ) - - countDowns.append({ - 'symbol': MARKET_SYMBOLS.ETH.value, - 'countDown': 3 * 1000 - } - ) - - countDownsObject["countDowns"] = countDowns - try: - # sending post request to reset user's count down timer with MARKET_SYMBOL for auto cancellation of order - postResponse = await client.reset_cancel_on_disconnect_timer(countDownsObject) - print(postResponse) - # get request to get user's count down timer for MARKET_SYMBOL - getResponse = await client.get_cancel_on_disconnect_timer({ - 'symbol': MARKET_SYMBOLS.ETH - }) - print(getResponse) - - except Exception as e: - print(e) - - await client.close_connections() + await client.close_connections() if __name__ == "__main__": - event_loop = asyncio.get_event_loop() - event_loop.run_until_complete(main()) + event_loop = asyncio.get_event_loop() + event_loop.run_until_complete(main()) diff --git a/examples/19.Generate_readonly_token.py b/examples/19.Generate_readonly_token.py index 083b73a..5d66925 100644 --- a/examples/19.Generate_readonly_token.py +++ b/examples/19.Generate_readonly_token.py @@ -1,37 +1,36 @@ +import sys, os -import sys,os -sys.path.append(os.getcwd()+"/src/") +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks +from bluefin_client_sui import BluefinClient, Networks from pprint import pprint import asyncio - async def main(): - # initialize client - client = FireflyClient( - True, # agree to terms and conditions - Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY, # private key of wallet - ) + # initialize client + client = BluefinClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + # initialize the client + # on boards user on Bluefin. Must be set to true for first time use + # + await client.init(True) - # initialize the client - # on boards user on firefly. Must be set to true for first time use - # - await client.init(True) - - print('Account Address:', client.get_public_address()) + print("Account Address:", client.get_public_address()) - # # generates read-only token for user - data = await client.generate_readonly_token() + # # generates read-only token for user + data = await client.generate_readonly_token() - print("Read-only Token:",str(data)) + print("Read-only Token:", str(data)) - await client.close_connections() + await client.close_connections() if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/2.user_info.py b/examples/2.user_info.py index 0e91289..1b6b65c 100644 --- a/examples/2.user_info.py +++ b/examples/2.user_info.py @@ -1,42 +1,43 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS +from bluefin_client_sui import BluefinClient, Networks, MARKET_SYMBOLS from pprint import pprint import asyncio + async def main(): # create client instance - client = FireflyClient( - True, # agree to terms and conditions - Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY, # private key of wallet - ) - + client = BluefinClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + # initialize the client - # on boards user on firefly. Must be set to true for first time use - await client.init(True) + # on boards user on Bluefin. Must be set to true for first time use + await client.init(True) - # gets user account data on firefly exchange + # gets user account data on Bluefin exchange data = await client.get_user_account_data() pprint(data) - position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) - + position = await client.get_user_position({"symbol": MARKET_SYMBOLS.ETH}) + # returns {} when user has no position pprint(position) - position = await client.get_user_position({"symbol":MARKET_SYMBOLS.BTC}) - + position = await client.get_user_position({"symbol": MARKET_SYMBOLS.BTC}) + # returns user position if exists pprint(position) await client.close_connections() - if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/20.contract_call.py b/examples/20.contract_call.py index 9277d37..2e01f4e 100644 --- a/examples/20.contract_call.py +++ b/examples/20.contract_call.py @@ -1,58 +1,57 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") import base64 from bluefin_client_sui.utilities import * from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + ORDER_SIDE, + ORDER_TYPE, + OrderSignatureRequest, +) import asyncio from bluefin_client_sui import signer from bluefin_client_sui import * -async def main(): +async def main(): # initialize client - client = FireflyClient( - True, # agree to terms and conditions - Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY, # private key of wallet - ) - await client.init(True) + client = BluefinClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + await client.init(True) ### you need to have usdc coins to deposit it to margin bank. ### the below functions just gets usdc coins that you have. - usdc_coins=client.get_usdc_coins() - - coin_obj_id=usdc_coins["data"][1]["coinObjectId"] - await client.deposit_margin_to_bank(1000, coin_obj_id) - + usdc_coins = client.get_usdc_coins() - #await client.withdraw_margin_from_bank(100) - - - #await client.withdraw_all_margin_from_bank() + coin_obj_id = usdc_coins["data"][1]["coinObjectId"] + await client.deposit_margin_to_bank(1000, coin_obj_id) - print ("Printing Margin Bank balance") - print (await client.get_margin_bank_balance()) + # await client.withdraw_margin_from_bank(100) - print ("Printing usdc balance") - print (await client.get_usdc_balance()) - - print ("Printing SUI balance") - print (await client.get_native_chain_token_balance()) - - - print ("getting usdc coins") - print (client.get_usdc_coins()) + # await client.withdraw_all_margin_from_bank() + print("Printing Margin Bank balance") + print(await client.get_margin_bank_balance()) + print("Printing usdc balance") + print(await client.get_usdc_balance()) + print("Printing SUI balance") + print(await client.get_native_chain_token_balance()) + print("getting usdc coins") + print(client.get_usdc_coins()) await client.close_connections() - - if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/3.balance.py b/examples/3.balance.py index 93a51ed..ea204ec 100644 --- a/examples/3.balance.py +++ b/examples/3.balance.py @@ -1,53 +1,53 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks +from bluefin_client_sui import BluefinClient, Networks import asyncio - async def main(): - # create client instance - client = FireflyClient( - True, # agree to terms and conditions - Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY, # private key of wallet - ) - - # initialize the client - # on boards user on firefly. Must be set to true for first time use - await client.init(True) + # create client instance + client = BluefinClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + # initialize the client + # on boards user on Bluefin. Must be set to true for first time use + await client.init(True) + + # checks chain native token balance. + # A user must have native tokens to perform contract calls + + print("Chain token balance:", await client.get_native_chain_token_balance()) - # checks chain native token balance. - # A user must have native tokens to perform contract calls - - print('Chain token balance:', await client.get_native_chain_token_balance()) + # check margin bank balance on-chain + print("Margin bank balance:", await client.get_margin_bank_balance()) - # check margin bank balance on-chain - print('Margin bank balance:', await client.get_margin_bank_balance()) + # check usdc balance user has on-chain + print("USDC balance:", await client.get_usdc_balance()) - # check usdc balance user has on-chain - print('USDC balance:', await client.get_usdc_balance()) + # deposit usdc to margin bank + # must have native chain tokens to pay for gas fee + print("USDC deposited:", await client.deposit_margin_to_bank(10)) - # deposit usdc to margin bank - # must have native chain tokens to pay for gas fee - print('USDC deposited:', await client.deposit_margin_to_bank(10)) + # check margin bank balance + resp = await client.get_margin_bank_balance() + print("Margin bank balance:", resp) - # check margin bank balance - resp = await client.get_margin_bank_balance() - print('Margin bank balance:', resp) + # withdraw margin bank balance + print("USDC Withdrawn:", await client.withdraw_margin_from_bank(resp)) - # withdraw margin bank balance - print('USDC Withdrawn:', await client.withdraw_margin_from_bank(resp)) + # check margin bank balance + print("Margin bank balance:", await client.get_margin_bank_balance()) - # check margin bank balance - print('Margin bank balance:', await client.get_margin_bank_balance()) - - await client.close_connections() + await client.close_connections() if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/4.placing_orders.py b/examples/4.placing_orders.py index 3b37f3f..1d17f97 100644 --- a/examples/4.placing_orders.py +++ b/examples/4.placing_orders.py @@ -1,28 +1,35 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + ORDER_SIDE, + ORDER_TYPE, + OrderSignatureRequest, +) import asyncio - -async def place_limit_order(client: FireflyClient): - # default leverage of account is set to 3 on firefly +async def place_limit_order(client: BluefinClient): + # default leverage of account is set to 3 on Bluefin user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) - print ("User Leverage", user_leverage) - + print("User Leverage", user_leverage) + # creates a LIMIT order to be signed signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol price=1636.8, # price at which you want to place order - quantity=0.01, # quantity - side=ORDER_SIDE.BUY, + quantity=0.01, # quantity + side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.LIMIT, - leverage=user_leverage - ) + leverage=user_leverage, + ) # create signed order - signed_order = client.create_signed_order(signature_request) + signed_order = client.create_signed_order(signature_request) print("Placing a limit order") # place signed order on orderbook @@ -33,29 +40,28 @@ async def place_limit_order(client: FireflyClient): return -async def place_market_order(client: FireflyClient): - - # default leverage of account is set to 3 on firefly +async def place_market_order(client: BluefinClient): + # default leverage of account is set to 3 on Bluefin user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, - price = 0, - quantity = 1, - leverage = 1, - side = ORDER_SIDE.BUY, - reduceOnly = False, - postOnly = False, - orderbookOnly = True, - maker = "0xa3c3504d90c428274beaa89f1238a769ea1d1c3516c31c0f4157f33787367af0", - expiration = 1700530261000, - salt = 1668690862116, - orderType = ORDER_TYPE.MARKET, + price=0, + quantity=1, + leverage=1, + side=ORDER_SIDE.BUY, + reduceOnly=False, + postOnly=False, + orderbookOnly=True, + maker="0xa3c3504d90c428274beaa89f1238a769ea1d1c3516c31c0f4157f33787367af0", + expiration=1700530261000, + salt=1668690862116, + orderType=ORDER_TYPE.MARKET, ) # create signed order - signed_order = client.create_signed_order(signature_request) + signed_order = client.create_signed_order(signature_request) print("Placing a market order") # place signed order on orderbook @@ -64,28 +70,27 @@ async def place_market_order(client: FireflyClient): # returned order with PENDING state print(resp) - return -async def main(): +async def main(): # initialize client - client = FireflyClient( - True, # agree to terms and conditions - Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY, # private key of wallet - ) + client = BluefinClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) - await client.init(True) + await client.init(True) # await place_limit_order(client) - await (client) + await client await place_limit_order(client) - + await client.close_connections() if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/5.adjusting_leverage.py b/examples/5.adjusting_leverage.py index 52d185a..1b5638b 100644 --- a/examples/5.adjusting_leverage.py +++ b/examples/5.adjusting_leverage.py @@ -1,48 +1,48 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS +from bluefin_client_sui import BluefinClient, Networks, MARKET_SYMBOLS import asyncio async def main(): - # initialize client - client = FireflyClient( - True, # agree to terms and conditions - Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY # private key of wallet - + client = BluefinClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet ) - await client.init(True, symbol= MARKET_SYMBOLS.BTC) + await client.init(True, symbol=MARKET_SYMBOLS.BTC) - print('Leverage on BTC market:', await client.get_user_leverage(MARKET_SYMBOLS.BTC)) + print("Leverage on BTC market:", await client.get_user_leverage(MARKET_SYMBOLS.BTC)) # we have a position on BTC so this will perform on-chain leverage update # must have native chain tokens to pay for gas fee - await client.adjust_leverage(MARKET_SYMBOLS.BTC, 6) + await client.adjust_leverage(MARKET_SYMBOLS.BTC, 6) - print('Leverage on BTC market:', await client.get_user_leverage(MARKET_SYMBOLS.BTC)) + print("Leverage on BTC market:", await client.get_user_leverage(MARKET_SYMBOLS.BTC)) - # initialize client - client = FireflyClient( - True, # agree to terms and conditions - Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY, # private key of wallet + # initialize client + client = BluefinClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet ) - await client.init(True, symbol=MARKET_SYMBOLS.ETH) + await client.init(True, symbol=MARKET_SYMBOLS.ETH) - print('Leverage on ETH market:', await client.get_user_leverage(MARKET_SYMBOLS.ETH)) + print("Leverage on ETH market:", await client.get_user_leverage(MARKET_SYMBOLS.ETH)) # since we don't have a position on-chain, it will perform off-chain leverage adjustment - await client.adjust_leverage(MARKET_SYMBOLS.ETH, 7) + await client.adjust_leverage(MARKET_SYMBOLS.ETH, 7) + + print("Leverage on ETH market:", await client.get_user_leverage(MARKET_SYMBOLS.ETH)) - print('Leverage on ETH market:', await client.get_user_leverage(MARKET_SYMBOLS.ETH)) - await client.close_connections() + if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/6.adjusting_margin.py b/examples/6.adjusting_margin.py index bde01a3..d9ce6d5 100644 --- a/examples/6.adjusting_margin.py +++ b/examples/6.adjusting_margin.py @@ -1,34 +1,42 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ADJUST_MARGIN +from bluefin_client_sui import BluefinClient, Networks, MARKET_SYMBOLS, ADJUST_MARGIN import asyncio -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest -TEST_NETWORK="SUI_STAGING" +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + ORDER_SIDE, + ORDER_TYPE, + OrderSignatureRequest, +) + +TEST_NETWORK = "SUI_STAGING" -async def place_limit_order(client: FireflyClient): - # default leverage of account is set to 3 on firefly +async def place_limit_order(client: BluefinClient): + # default leverage of account is set to 3 on Bluefin user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) await client.adjust_leverage(MARKET_SYMBOLS.ETH, 3) user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) - print ("User Leverage", user_leverage) - + print("User Leverage", user_leverage) # creates a LIMIT order to be signed signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol price=1636.8, # price at which you want to place order - quantity=0.01, # quantity - side=ORDER_SIDE.BUY, + quantity=0.01, # quantity + side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.LIMIT, - leverage=user_leverage - ) + leverage=user_leverage, + ) # create signed order - signed_order = client.create_signed_order(signature_request) + signed_order = client.create_signed_order(signature_request) print("Placing a limit order") # place signed order on orderbook @@ -39,23 +47,22 @@ async def place_limit_order(client: FireflyClient): return -async def place_market_order(client: FireflyClient): - - # default leverage of account is set to 3 on firefly +async def place_market_order(client: BluefinClient): + # default leverage of account is set to 3 on Bluefin user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.ETH) signature_request = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, - price = 0, - quantity = 1, - leverage = user_leverage, - side = ORDER_SIDE.BUY, + price=0, + quantity=1, + leverage=user_leverage, + side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.MARKET, ) # create signed order - signed_order = client.create_signed_order(signature_request) + signed_order = client.create_signed_order(signature_request) print("Placing a market order") # place signed order on orderbook @@ -64,45 +71,43 @@ async def place_market_order(client: FireflyClient): # returned order with PENDING state print(resp) - return -async def main(): - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + +async def main(): + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - print (await client.get_usdc_balance()) - - #usdc_coins=client.get_usdc_coins() - #coin_obj_id=usdc_coins["data"][1]["coinObjectId"] - #await client.deposit_margin_to_bank(1000000000000, coin_obj_id) - - print (await client.get_margin_bank_balance()) + print(await client.get_usdc_balance()) + + # usdc_coins=client.get_usdc_coins() + # coin_obj_id=usdc_coins["data"][1]["coinObjectId"] + # await client.deposit_margin_to_bank(1000000000000, coin_obj_id) + + print(await client.get_margin_bank_balance()) await place_market_order(client) - position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) + position = await client.get_user_position({"symbol": MARKET_SYMBOLS.ETH}) print("Current margin in position:", position) # adding 100$ from our margin bank into our BTC position on-chain # must have native chain tokens to pay for gas fee - await client.adjust_margin(MARKET_SYMBOLS.ETH, ADJUST_MARGIN.ADD, 100) + await client.adjust_margin(MARKET_SYMBOLS.ETH, ADJUST_MARGIN.ADD, 100) # get updated position margin. Note it can take a few seconds to show updates # to on-chain positions on exchange as off-chain infrastructure waits for blockchain # to emit position update event - position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) - print("Current margin in position:",position["margin"]) - + position = await client.get_user_position({"symbol": MARKET_SYMBOLS.ETH}) + print("Current margin in position:", position["margin"]) # removing 100$ from margin - await client.adjust_margin(MARKET_SYMBOLS.ETH, ADJUST_MARGIN.REMOVE, 100) + await client.adjust_margin(MARKET_SYMBOLS.ETH, ADJUST_MARGIN.REMOVE, 100) - position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) + position = await client.get_user_position({"symbol": MARKET_SYMBOLS.ETH}) print("Current margin in position:", int(position["margin"])) - try: # will throw as user does not have any open position on BTC to adjust margin on - await client.adjust_margin(MARKET_SYMBOLS.BTC, ADJUST_MARGIN.ADD, 100) + await client.adjust_margin(MARKET_SYMBOLS.BTC, ADJUST_MARGIN.ADD, 100) except Exception as e: print("Error:", e) @@ -110,6 +115,6 @@ async def main(): if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/7.cancelling_orders.py b/examples/7.cancelling_orders.py index 5659cc9..b47f07c 100644 --- a/examples/7.cancelling_orders.py +++ b/examples/7.cancelling_orders.py @@ -1,60 +1,68 @@ -import sys,os, random -sys.path.append(os.getcwd()+"/src/") +import sys, os, random + +sys.path.append(os.getcwd() + "/src/") import time from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, ORDER_STATUS, OrderSignatureRequest +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + ORDER_SIDE, + ORDER_TYPE, + ORDER_STATUS, + OrderSignatureRequest, +) from pprint import pprint import asyncio - async def main(): - - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - #client.create_order_to_sign() - await client.adjust_leverage(MARKET_SYMBOLS.ETH, 1) + # client.create_order_to_sign() + await client.adjust_leverage(MARKET_SYMBOLS.ETH, 1) - - # creates a LIMIT order to be signed + # creates a LIMIT order to be signed order = OrderSignatureRequest( symbol=MARKET_SYMBOLS.ETH, # market symbol price=1636.8, # price at which you want to place order - quantity=0.01, # quantity - side=ORDER_SIDE.BUY, + quantity=0.01, # quantity + side=ORDER_SIDE.BUY, orderType=ORDER_TYPE.LIMIT, leverage=1, - salt=random.randint(0,100000000), - expiration=int(time.time()+(30*24*60*60))*1000 - ) + salt=random.randint(0, 100000000), + expiration=int(time.time() + (30 * 24 * 60 * 60)) * 1000, + ) - - signed_order = client.create_signed_order(order) + signed_order = client.create_signed_order(order) resp = await client.post_signed_order(signed_order) - print ("sleeping for two seconds") - - + print("sleeping for two seconds") + # sign order for cancellation using order hash # you can pass a list of hashes to be signed for cancellation, good to be used when multiple orders are to be cancelled - cancellation_request = client.create_signed_cancel_orders(MARKET_SYMBOLS.ETH, order_hash=[resp['hash']]) + cancellation_request = client.create_signed_cancel_orders( + MARKET_SYMBOLS.ETH, order_hash=[resp["hash"]] + ) pprint(cancellation_request) # # or sign the order for cancellation using order data cancellation_request = client.create_signed_cancel_order(order) - pprint(cancellation_request) # same as above cancellation request + pprint(cancellation_request) # same as above cancellation request # post order to exchange for cancellation resp = await client.post_cancel_order(cancellation_request) - + pprint(resp) # cancels all open orders, returns false if there is no open order to cancel - resp = await client.cancel_all_orders(MARKET_SYMBOLS.ETH, [ORDER_STATUS.OPEN, ORDER_STATUS.PARTIAL_FILLED]) + resp = await client.cancel_all_orders( + MARKET_SYMBOLS.ETH, [ORDER_STATUS.OPEN, ORDER_STATUS.PARTIAL_FILLED] + ) if resp == False: - print('No open order to cancel') + print("No open order to cancel") else: pprint(resp) @@ -62,6 +70,6 @@ async def main(): if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/8.exchange_data.py b/examples/8.exchange_data.py index 35b4d06..da56722 100644 --- a/examples/8.exchange_data.py +++ b/examples/8.exchange_data.py @@ -1,24 +1,29 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, TRADE_TYPE, Interval +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + TRADE_TYPE, + Interval, +) from pprint import pprint import asyncio async def main(): - - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - # returns status/health of exchange status = await client.get_exchange_status() pprint(status) # gets state of order book. Gets first 10 asks and bids - orderbook = await client.get_orderbook({"symbol": MARKET_SYMBOLS.ETH, "limit":10}) + orderbook = await client.get_orderbook({"symbol": MARKET_SYMBOLS.ETH, "limit": 10}) pprint(orderbook) # returns available market for trading @@ -30,7 +35,7 @@ async def main(): pprint(funding_rate) # gets markets meta data about contracts, blockchain, exchange url - meta = await client.get_market_meta_info() # (optional param MARKET_SYMBOL) + meta = await client.get_market_meta_info() # (optional param MARKET_SYMBOL) # should log meta for all markets pprint(meta) @@ -38,20 +43,22 @@ async def main(): market_data = await client.get_market_data(MARKET_SYMBOLS.ETH) pprint(market_data) - # gets market data about min/max order size, oracle price, fee etc.. exchange_info = await client.get_exchange_info(MARKET_SYMBOLS.ETH) pprint(exchange_info) # gets market candle info - candle_data = await client.get_market_candle_stick_data({"symbol": MARKET_SYMBOLS.ETH, "interval": Interval._1M}) + candle_data = await client.get_market_candle_stick_data( + {"symbol": MARKET_SYMBOLS.ETH, "interval": Interval._1M} + ) pprint(candle_data) # gets recent isolated/normal trades on ETH market - recent_trades = await client.get_market_recent_trades({"symbol": MARKET_SYMBOLS.ETH, "traders": TRADE_TYPE.ISOLATED}) + recent_trades = await client.get_market_recent_trades( + {"symbol": MARKET_SYMBOLS.ETH, "traders": TRADE_TYPE.ISOLATED} + ) pprint(recent_trades) - # gets addresses of on-chain contracts contract_address = await client.get_contract_addresses() pprint(contract_address) @@ -60,6 +67,6 @@ async def main(): if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/9.user_data.py b/examples/9.user_data.py index d9b88d3..ea9df5d 100644 --- a/examples/9.user_data.py +++ b/examples/9.user_data.py @@ -1,67 +1,70 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_STATUS +from bluefin_client_sui import BluefinClient, Networks, MARKET_SYMBOLS, ORDER_STATUS from pprint import pprint import asyncio -TEST_NETWORK="SUI_STAGING" -async def main(): +TEST_NETWORK = "SUI_STAGING" + - client = FireflyClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) +async def main(): + client = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY) await client.init(True) - # returns user account (having pvt key and pub address) user_account = client.get_account() - print('account:', user_account) + print("account:", user_account) # returns user public address pub_address = client.get_public_address() - print('pub_address:', pub_address) + print("pub_address:", pub_address) # used to fetch user orders. Pass in statuses of orders to get - orders = await client.get_orders({ - "symbol": MARKET_SYMBOLS.ETH, - "statuses": [ORDER_STATUS.OPEN, ORDER_STATUS.PENDING] - }) + orders = await client.get_orders( + { + "symbol": MARKET_SYMBOLS.ETH, + "statuses": [ORDER_STATUS.OPEN, ORDER_STATUS.PENDING], + } + ) print("User open and pending orders:") pprint(orders) # fetches user transaction history. Pass page number and size as the route is paginated - tx_history = await client.get_transaction_history({ - "symbol": MARKET_SYMBOLS.ETH, - }) - print("User transaction history:") + tx_history = await client.get_transaction_history( + { + "symbol": MARKET_SYMBOLS.ETH, + } + ) + print("User transaction history:") pprint(tx_history) # gets user current position - position = await client.get_user_position({"symbol":MARKET_SYMBOLS.ETH}) + position = await client.get_user_position({"symbol": MARKET_SYMBOLS.ETH}) - print("User position:") + print("User position:") pprint(position) # fetches user trades - trades = await client.get_user_trades({"symbol":MARKET_SYMBOLS.BTC}) - print("User trades:") + trades = await client.get_user_trades({"symbol": MARKET_SYMBOLS.BTC}) + print("User trades:") pprint(trades) - # fetches user account's general data like leverage, pnl etc. account_data = await client.get_user_account_data() - print("Account data:") + print("Account data:") pprint(account_data) - user_leverage = await client.get_user_leverage(MARKET_SYMBOLS.BTC) - print("Account leverage:", user_leverage) + print("Account leverage:", user_leverage) await client.close_connections() if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/SocketClient.py b/examples/SocketClient.py index bbeef62..fd1f627 100644 --- a/examples/SocketClient.py +++ b/examples/SocketClient.py @@ -1,22 +1,25 @@ -import socketio +import socketio # Create a Socket.IO client instance sio = socketio.Client() + # Define an event handler for the 'connect' event @sio.event def connect(): - print('Connected to server') + print("Connected to server") # Subscribe to the 'message' event # sio.emit('subscribeToRoom', "room1") + # Define an event handler for the 'message' event @sio.event def roomMessage(data): - print('Received message:', data) + print("Received message:", data) + # Connect to the Socket.IO server -sio.connect('http://localhost:3000') +sio.connect("http://localhost:3000") # Wait for events and keep the connection alive -sio.wait() \ No newline at end of file +sio.wait() diff --git a/examples/SocketServer.py b/examples/SocketServer.py index 7843523..2c9c805 100644 --- a/examples/SocketServer.py +++ b/examples/SocketServer.py @@ -5,25 +5,28 @@ # Create a Socket.IO server instance sio = socketio.Server() + # Define an event handler for the 'connect' event -@sio.on('connect') +@sio.on("connect") def handle_connect(sid, environ): - print('Client connected:', sid) + print("Client connected:", sid) background_task() - + # Define an event handler for the 'message' event -@sio.on('message') +@sio.on("message") def handle_message(sid, data): - print('Received message:', data) + print("Received message:", data) # Broadcast the received message to all connected clients - sio.emit('message', data) + sio.emit("message", data) + # Define the broadcast task def broadcast_message(): message = "Hello from the server!" print(message) - sio.emit('message', message, namespace='/') + sio.emit("message", message, namespace="/") + # Background task to broadcast messages at an interval def background_task(): @@ -32,11 +35,10 @@ def background_task(): time.sleep(5) # Adjust the interval as needed broadcast_message() + # Run the Socket.IO server -if __name__ == '__main__': +if __name__ == "__main__": app = socketio.WSGIApp(sio) socketio.Middleware(sio, app) - eventlet.wsgi.server(eventlet.listen(('', 3051)), app) + eventlet.wsgi.server(eventlet.listen(("", 3051)), app) print("hello after") - - diff --git a/examples/config.py b/examples/config.py index b5452c2..77f4011 100644 --- a/examples/config.py +++ b/examples/config.py @@ -1,4 +1,6 @@ -TEST_ACCT_KEY="negative repeat fold noodle symptom spirit spend trophy merge ethics math erupt" -#TEST_ACCT_KEY = "milk fit tape notable input seek circle define deny rally camera sorry" +TEST_ACCT_KEY = ( + "negative repeat fold noodle symptom spirit spend trophy merge ethics math erupt" +) +# TEST_ACCT_KEY = "milk fit tape notable input seek circle define deny rally camera sorry" TEST_SUB_ACCT_KEY = "7540d48032c731b3a17947b63a04763492d84aef854246d355a703adc9b54ce9" -TEST_NETWORK = "SUI_STAGING" \ No newline at end of file +TEST_NETWORK = "SUI_STAGING" diff --git a/examples/contract_call.py b/examples/contract_call.py index e6dbba8..2a632ee 100644 --- a/examples/contract_call.py +++ b/examples/contract_call.py @@ -1,47 +1,52 @@ -import sys,os -sys.path.append(os.getcwd()+"/src/") +import sys, os + +sys.path.append(os.getcwd() + "/src/") import base64 from bluefin_client_sui.utilities import * from config import TEST_ACCT_KEY, TEST_NETWORK -from bluefin_client_sui import FireflyClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest +from bluefin_client_sui import ( + BluefinClient, + Networks, + MARKET_SYMBOLS, + ORDER_SIDE, + ORDER_TYPE, + OrderSignatureRequest, +) import asyncio from bluefin_client_sui import signer from bluefin_client_sui import * -async def main(): +async def main(): # initialize client - client = FireflyClient( - True, # agree to terms and conditions - Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY, # private key of wallet - ) - await client.init(True) + client = BluefinClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + await client.init(True) await client.withdraw_margin_from_bank(1000) - txBytes="AAADAQGPzuAZV5krLKJWD1WUXOjk7Guz2vki2pBMZJ6CXkRf1XLGgQAAAAAAAQAgH/qFdX+Vzs7ShiLTDkGkUsiFFt9TRQyxkTgS3YKNWWgAEOgDAAAAAAAAAAAAAAAAAAABAF82iNaL7Cly31h0P767ErFoKbQb8bxQeNNRAJDvbPWOC21hcmdpbl9iYW5rEndpdGhkcmF3X2Zyb21fYmFuawADAQAAAQEAAQIAH/qFdX+Vzs7ShiLTDkGkUsiFFt9TRQyxkTgS3YKNWWgBWiybg/9fRf7zXUmsdBU3umIFeucEZNWeMGzlLttPf86tHg8AAAAAACA3qZfzxP1+yVILtVkJ6LdfZvkF7gK877AJco9Xook9Th/6hXV/lc7O0oYi0w5BpFLIhRbfU0UMsZE4Et2CjVlo6AMAAAAAAAAA4fUFAAAAAAA=" - dec_msg=base64.b64decode(txBytes) - mysigner=signer.Signer() - - seed="negative repeat fold noodle symptom spirit spend trophy merge ethics math erupt" - sui_wallet=SuiWallet(seed=seed) + txBytes = "AAADAQGPzuAZV5krLKJWD1WUXOjk7Guz2vki2pBMZJ6CXkRf1XLGgQAAAAAAAQAgH/qFdX+Vzs7ShiLTDkGkUsiFFt9TRQyxkTgS3YKNWWgAEOgDAAAAAAAAAAAAAAAAAAABAF82iNaL7Cly31h0P767ErFoKbQb8bxQeNNRAJDvbPWOC21hcmdpbl9iYW5rEndpdGhkcmF3X2Zyb21fYmFuawADAQAAAQEAAQIAH/qFdX+Vzs7ShiLTDkGkUsiFFt9TRQyxkTgS3YKNWWgBWiybg/9fRf7zXUmsdBU3umIFeucEZNWeMGzlLttPf86tHg8AAAAAACA3qZfzxP1+yVILtVkJ6LdfZvkF7gK877AJco9Xook9Th/6hXV/lc7O0oYi0w5BpFLIhRbfU0UMsZE4Et2CjVlo6AMAAAAAAAAA4fUFAAAAAAA=" + dec_msg = base64.b64decode(txBytes) + mysigner = signer.Signer() - #private_key=mnemonicToPrivateKey(seed) - #privateKeyBytes=private_key.ToBytes() - #publicKey=privateKeyToPublicKey(private_key) - #publicKeyBytes=publicKey.ToBytes() + seed = "negative repeat fold noodle symptom spirit spend trophy merge ethics math erupt" + sui_wallet = SuiWallet(seed=seed) + # private_key=mnemonicToPrivateKey(seed) + # privateKeyBytes=private_key.ToBytes() + # publicKey=privateKeyToPublicKey(private_key) + # publicKeyBytes=publicKey.ToBytes() - result=mysigner.sign_tx(dec_msg, sui_wallet) - print (result) + result = mysigner.sign_tx(dec_msg, sui_wallet) + print(result) await client.close_connections() - - if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/pyproject.toml b/pyproject.toml index 75ebff0..79814bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "bluefin_client_sui" -version = "1.0.0" +version = "2.0.0" description = "Library to interact with firefly exchange protocol including its off-chain api-gateway and on-chain contracts" readme = "README.md" requires-python = ">=3.8" diff --git a/src/bluefin_client_sui/__init__.py b/src/bluefin_client_sui/__init__.py index 5387318..9b2492c 100644 --- a/src/bluefin_client_sui/__init__.py +++ b/src/bluefin_client_sui/__init__.py @@ -3,4 +3,4 @@ from .constants import * from .enumerations import * from .interfaces import * -from .utilities import * \ No newline at end of file +from .utilities import * diff --git a/src/bluefin_client_sui/account.py b/src/bluefin_client_sui/account.py index d46c2b8..b4a8ac5 100644 --- a/src/bluefin_client_sui/account.py +++ b/src/bluefin_client_sui/account.py @@ -1,38 +1,36 @@ - from .utilities import * import base64 -class SuiWallet(): + + +class SuiWallet: def __init__(self, seed="", privateKey=""): - if seed=="" and privateKey=="": + if seed == "" and privateKey == "": return "Error" - if seed!="": - self.privateKey=mnemonicToPrivateKey(seed) - self.publicKey=privateKeyToPublicKey(self.privateKey) - self.key=self.getPrivateKey() - elif privateKey!="": - self.privateKey=privateKey - self.publicKey=privateKeyToPublicKey(self.privateKey) - self.key=self.getPrivateKey() - + if seed != "": + self.privateKey = mnemonicToPrivateKey(seed) + self.publicKey = privateKeyToPublicKey(self.privateKey) + self.key = self.getPrivateKey() + elif privateKey != "": + self.privateKey = privateKey + self.publicKey = privateKeyToPublicKey(self.privateKey) + self.key = self.getPrivateKey() + else: return "error" - - self.publicKeyBase64=base64.b64encode(self.publicKey.ToBytes()[1:]) - self.privateKeyBase64=base64.b64encode(self.privateKey.ToBytes()[1:]) - - self.privateKeyBytes=self.privateKey.ToBytes() - self.publicKeyBytes=self.publicKey.ToBytes() - - self.address=getAddressFromPublicKey(self.publicKey) + self.publicKeyBase64 = base64.b64encode(self.publicKey.ToBytes()[1:]) + self.privateKeyBase64 = base64.b64encode(self.privateKey.ToBytes()[1:]) + + self.privateKeyBytes = self.privateKey.ToBytes() + self.publicKeyBytes = self.publicKey.ToBytes() + + self.address = getAddressFromPublicKey(self.publicKey) def getPublicKey(self): return self.publicKey.ToHex() - def getPrivateKey(self): return self.privateKey.ToHex() - + def getUserAddress(self): return self.address - \ No newline at end of file diff --git a/src/bluefin_client_sui/api_service.py b/src/bluefin_client_sui/api_service.py index 42da73c..35b5a10 100644 --- a/src/bluefin_client_sui/api_service.py +++ b/src/bluefin_client_sui/api_service.py @@ -3,7 +3,7 @@ from .interfaces import * -class APIService(): +class APIService: def __init__(self, url, UUID=""): self.server_url = url self.auth_token = None @@ -17,11 +17,11 @@ async def close_session(self): async def get(self, service_url, query={}, auth_required=False): """ - Makes a GET request and returns the results - Inputs: - - service_url(str): the url to make the request to. - - query(dict): the get query. - - auth_required(bool): indicates whether authorization is required for the call or not. + Makes a GET request and returns the results + Inputs: + - service_url(str): the url to make the request to. + - query(dict): the get query. + - auth_required(bool): indicates whether authorization is required for the call or not. """ url = self._create_url(service_url) @@ -31,11 +31,12 @@ async def get(self, service_url, query={}, auth_required=False): url, params=query, headers={ - 'Authorization': 'Bearer {}'.format(self.auth_token) if self.auth_token else '', - 'x-api-token': self.api_token or '', - 'x-mm-id': self.uuid or '' - - } + "Authorization": "Bearer {}".format(self.auth_token) + if self.auth_token + else "", + "x-api-token": self.api_token or "", + "x-mm-id": self.uuid or "", + }, ) else: response = await self.client.get(url, params=query) @@ -50,22 +51,22 @@ async def get(self, service_url, query={}, auth_required=False): async def post(self, service_url, data, auth_required=False, contentType=""): """ - Makes a POST request and returns the results - Inputs: - - service_url(str): the url to make the request to. - - data(dict): the data to post with POST request. - - auth_required(bool): indicates whether authorization is required for the call or not. + Makes a POST request and returns the results + Inputs: + - service_url(str): the url to make the request to. + - data(dict): the data to post with POST request. + - auth_required(bool): indicates whether authorization is required for the call or not. """ url = self._create_url(service_url) response = None if auth_required: headers = { - 'Authorization': 'Bearer {}'.format(self.auth_token), - 'x-mm-id': self.uuid or '' + "Authorization": "Bearer {}".format(self.auth_token), + "x-mm-id": self.uuid or "", } if contentType is not "": - headers['Content-type'] = contentType + headers["Content-type"] = contentType response = await self.client.post(url=url, data=data, headers=headers) else: @@ -77,16 +78,15 @@ async def post(self, service_url, data, auth_required=False, contentType=""): else: return response except: - raise Exception( - "Error while posting to {}: {}".format(url, response)) + raise Exception("Error while posting to {}: {}".format(url, response)) async def delete(self, service_url, data, auth_required=False): """ - Makes a DELETE request and returns the results - Inputs: - - service_url(str): the url to make the request to. - - data(dict): the data to post with POST request. - - auth_required(bool): indicates whether authorization is required for the call or not. + Makes a DELETE request and returns the results + Inputs: + - service_url(str): the url to make the request to. + - data(dict): the data to post with POST request. + - auth_required(bool): indicates whether authorization is required for the call or not. """ url = self._create_url(service_url) @@ -95,23 +95,25 @@ async def delete(self, service_url, data, auth_required=False): response = await self.client.delete( url=url, data=data, - headers={'Authorization': 'Bearer {}'.format(self.auth_token), - 'x-mm-id': self.uuid or ''}) + headers={ + "Authorization": "Bearer {}".format(self.auth_token), + "x-mm-id": self.uuid or "", + }, + ) else: response = await self.client.delete(url=url, data=data) try: return await response.json() except: - raise Exception( - "Error while posting to {}: {}".format(url, response)) + raise Exception("Error while posting to {}: {}".format(url, response)) - ''' + """ Private methods - ''' + """ def _create_url(self, path): """ - Appends namespace to server url + Appends namespace to server url """ return "{}{}".format(self.server_url, path) diff --git a/src/bluefin_client_sui/client.py b/src/bluefin_client_sui/client.py index 40f6717..4b11c4a 100644 --- a/src/bluefin_client_sui/client.py +++ b/src/bluefin_client_sui/client.py @@ -15,37 +15,39 @@ from .interfaces import * from .enumerations import * -_SUI_BASE_NUM=1000000000 -class FireflyClient: +_SUI_BASE_NUM = 1000000000 + + +class BluefinClient: def __init__(self, are_terms_accepted, network, private_key=""): self.are_terms_accepted = are_terms_accepted self.network = network if private_key != "": - #currently we only support seed phrase - self.account=SuiWallet(seed=private_key) - #self.account = Account.from_key(private_key) - self.apis = APIService(self.network["apiGateway"], default_value(self.network, "UUID", "") ) + # currently we only support seed phrase + self.account = SuiWallet(seed=private_key) + # self.account = Account.from_key(private_key) + self.apis = APIService( + self.network["apiGateway"], default_value(self.network, "UUID", "") + ) self.dms_api = APIService(self.network["dmsURL"]) self.socket = Sockets(self.network["socketURL"]) self.ws_client = WebsocketClient(self.network["webSocketURL"]) self.contracts = Contracts() self.order_signer = OrderSigner() self.onboarding_signer = OnboardingSigner() - self.contract_signer=Signer() - self.url=self.network['url'] - - + self.contract_signer = Signer() + self.url = self.network["url"] + async def init(self, user_onboarding=True, api_token=""): """ - Initialize the client. - Inputs: - user_onboarding (bool, optional): If set to true onboards the user address to exchange and gets authToken. Defaults to True. - api_token(string, optional): API token to initialize client in read-only mode + Initialize the client. + Inputs: + user_onboarding (bool, optional): If set to true onboards the user address to exchange and gets authToken. Defaults to True. + api_token(string, optional): API token to initialize client in read-only mode """ contract_info = await self.get_contract_addresses() self.contracts.set_contract_addresses(contract_info) - if api_token: self.apis.api_token = api_token # for socket @@ -58,40 +60,42 @@ async def init(self, user_onboarding=True, api_token=""): self.socket.set_token(self.apis.auth_token) self.ws_client.set_token(self.apis.auth_token) - - async def onboard_user(self, token:str=None): + async def onboard_user(self, token: str = None): """ - On boards the user address and returns user authentication token. - Inputs: - token: user access token, if you possess one. - Returns: - str: user authorization token + On boards the user address and returns user authentication token. + Inputs: + token: user access token, if you possess one. + Returns: + str: user authorization token """ user_auth_token = token - + # if no auth token provided create on if not user_auth_token: onboarding_signature = self.onboarding_signer.create_signature( - self.network["onboardingUrl"], - self.account.privateKeyBytes + self.network["onboardingUrl"], self.account.privateKeyBytes + ) + onboarding_signature = ( + onboarding_signature + self.account.publicKeyBase64.decode() + ) + response = await self.authorize_signed_hash(onboarding_signature) + + if "error" in response: + raise SystemError( + f"Authorization error: {response['error']['message']}" ) - onboarding_signature=onboarding_signature+self.account.publicKeyBase64.decode() - response = await self.authorize_signed_hash(onboarding_signature) - - if 'error' in response: - raise SystemError(f"Authorization error: {response['error']['message']}") - user_auth_token = response['token'] + user_auth_token = response["token"] return user_auth_token - async def authorize_signed_hash(self, signed_hash:str): + async def authorize_signed_hash(self, signed_hash: str): """ - Registers user as an authorized user on server and returns authorization token. - Inputs: - signed_hash: signed onboarding hash - Returns: - dict: response from user authorization API Firefly + Registers user as an authorized user on server and returns authorization token. + Inputs: + signed_hash: signed onboarding hash + Returns: + dict: response from user authorization API Bluefin """ return await self.apis.post( SERVICE_URLS["USER"]["AUTHORIZE"], @@ -99,49 +103,54 @@ async def authorize_signed_hash(self, signed_hash:str): "signature": signed_hash, "userAddress": self.account.address, "isTermAccepted": self.are_terms_accepted, - }) + }, + ) - def create_order_to_sign(self, params:OrderSignatureRequest): + def create_order_to_sign(self, params: OrderSignatureRequest): """ - Creates order signature request for an order. - Inputs: - params (OrderSignatureRequest): parameters to create order with, refer OrderSignatureRequest - - Returns: - Order: order raw info + Creates order signature request for an order. + Inputs: + params (OrderSignatureRequest): parameters to create order with, refer OrderSignatureRequest + + Returns: + Order: order raw info """ - expiration = current_unix_timestamp() + expiration = current_unix_timestamp() # MARKET ORDER set expiration of 1 minute - if (params["orderType"] == ORDER_TYPE.MARKET): + if params["orderType"] == ORDER_TYPE.MARKET: expiration += TIME["SECONDS_IN_A_MINUTE"] - expiration*=1000 + expiration *= 1000 # LIMIT ORDER set expiration of 30 days else: expiration += TIME["SECONDS_IN_A_MONTH"] - expiration*=1000 - - return Order ( - market = default_value(params,'market',self.contracts.get_perpetual_id(params['symbol'])), - isBuy = params["side"] == ORDER_SIDE.BUY, - price = params["price"], - quantity = params["quantity"], - leverage = default_value(params, "leverage", 1), - maker = params["maker"].lower() if "maker" in params else self.account.address.lower(), - reduceOnly = default_value(params, "reduceOnly", False), - postOnly = default_value(params,"postOnly",False), - orderbookOnly = default_value(params, "orderbookOnly",True), - expiration = default_value(params, "expiration", expiration), - salt = default_value(params, "salt", random_number(1000000)), - ioc = default_value(params,"ioc", False ) - ) + expiration *= 1000 + + return Order( + market=default_value( + params, "market", self.contracts.get_perpetual_id(params["symbol"]) + ), + isBuy=params["side"] == ORDER_SIDE.BUY, + price=params["price"], + quantity=params["quantity"], + leverage=default_value(params, "leverage", 1), + maker=params["maker"].lower() + if "maker" in params + else self.account.address.lower(), + reduceOnly=default_value(params, "reduceOnly", False), + postOnly=default_value(params, "postOnly", False), + orderbookOnly=default_value(params, "orderbookOnly", True), + expiration=default_value(params, "expiration", expiration), + salt=default_value(params, "salt", random_number(1000000)), + ioc=default_value(params, "ioc", False), + ) - def create_signed_order(self, req:OrderSignatureRequest) -> OrderSignatureResponse: + def create_signed_order(self, req: OrderSignatureRequest) -> OrderSignatureResponse: """ - Create an order from provided params and signs it using the private key of the account - Inputs: - params (OrderSignatureRequest): parameters to create order with - Returns: - OrderSignatureResponse: order raw info and generated signature + Create an order from provided params and signs it using the private key of the account + Inputs: + params (OrderSignatureRequest): parameters to create order with + Returns: + OrderSignatureResponse: order raw info and generated signature """ sui_params = deepcopy(req) sui_params["price"] = self._to_sui_base(req["price"]) @@ -150,8 +159,10 @@ def create_signed_order(self, req:OrderSignatureRequest) -> OrderSignatureRespon order = self.create_order_to_sign(sui_params) symbol = sui_params["symbol"].value - order_signature = self.order_signer.sign_order(order, self.account.privateKeyBytes) - order_signature=order_signature+self.account.publicKeyBase64.decode() + order_signature = self.order_signer.sign_order( + order, self.account.privateKeyBytes + ) + order_signature = order_signature + self.account.publicKeyBase64.decode() return OrderSignatureResponse( symbol=symbol, price=sui_params["price"], @@ -164,10 +175,12 @@ def create_signed_order(self, req:OrderSignatureRequest) -> OrderSignatureRespon orderSignature=order_signature, orderType=sui_params["orderType"], maker=order["maker"], - orderbookOnly=default_value(sui_params,'orderbookOnly',True) + orderbookOnly=default_value(sui_params, "orderbookOnly", True), ) - - def create_signed_cancel_order(self,params:OrderSignatureRequest, parentAddress:str=""): + + def create_signed_cancel_order( + self, params: OrderSignatureRequest, parentAddress: str = "" + ): """ Creates a cancel order request from provided params and signs it using the private key of the account @@ -177,13 +190,17 @@ def create_signed_cancel_order(self,params:OrderSignatureRequest, parentAddress: parentAddress (str): Only provided by a sub account Returns: - OrderSignatureResponse: generated cancel signature + OrderSignatureResponse: generated cancel signature """ order_to_sign = self.create_order_to_sign(params) hash_val = self.order_signer.get_order_hash(order_to_sign, withBufferHex=False) - return self.create_signed_cancel_orders(params["symbol"], hash_val.hex(), parentAddress) + return self.create_signed_cancel_orders( + params["symbol"], hash_val.hex(), parentAddress + ) - def create_signed_cancel_orders(self, symbol:MARKET_SYMBOLS, order_hash:list, parentAddress:str=""): + def create_signed_cancel_orders( + self, symbol: MARKET_SYMBOLS, order_hash: list, parentAddress: str = "" + ): """ Creates a cancel order from provided params and sign it using the private key of the account @@ -196,214 +213,222 @@ def create_signed_cancel_orders(self, symbol:MARKET_SYMBOLS, order_hash:list, pa """ if isinstance(order_hash, list) is False: order_hash = [order_hash] - cancel_hash = self.order_signer.encode_message({'orderHashes' : order_hash}) - hash_sig = self.order_signer.sign_hash(cancel_hash,self.account.privateKeyBytes,"")+self.account.publicKeyBase64.decode() + cancel_hash = self.order_signer.encode_message({"orderHashes": order_hash}) + hash_sig = ( + self.order_signer.sign_hash(cancel_hash, self.account.privateKeyBytes, "") + + self.account.publicKeyBase64.decode() + ) return OrderCancellationRequest( symbol=symbol.value, hashes=order_hash, signature=hash_sig, - parentAddress=parentAddress + parentAddress=parentAddress, ) - async def post_cancel_order(self,params:OrderCancellationRequest): + async def post_cancel_order(self, params: OrderCancellationRequest): """ - POST cancel order request to Firefly - Inputs: - params(dict): a dictionary with OrderCancellationRequest required params - Returns: - dict: response from orders delete API Firefly + POST cancel order request to Bluefin + Inputs: + params(dict): a dictionary with OrderCancellationRequest required params + Returns: + dict: response from orders delete API Bluefin """ return await self.apis.delete( SERVICE_URLS["ORDERS"]["ORDERS_HASH"], { "symbol": params["symbol"], - "orderHashes":params["hashes"], - "cancelSignature":params["signature"], + "orderHashes": params["hashes"], + "cancelSignature": params["signature"], "parentAddress": params["parentAddress"], }, - auth_required=True - ) - - async def cancel_all_orders(self, symbol:MARKET_SYMBOLS, status: List[ORDER_STATUS], parentAddress:str=""): - """ - GETs all orders of specified status for the specified symbol, - and creates a cancellation request for all orders and - POSTs the cancel order request to Firefly - Inputs: - symbol (MARKET_SYMBOLS): Market for which orders are to be cancelled - status (List[ORDER_STATUS]): status of orders that need to be cancelled - parentAddress (str): address of parent account, only provided by sub account - Returns: - dict: response from orders delete API Firefly - """ - orders = await self.get_orders({ - "symbol":symbol, - "parentAddress": parentAddress, - "statuses":status - }) + auth_required=True, + ) + + async def cancel_all_orders( + self, + symbol: MARKET_SYMBOLS, + status: List[ORDER_STATUS], + parentAddress: str = "", + ): + """ + GETs all orders of specified status for the specified symbol, + and creates a cancellation request for all orders and + POSTs the cancel order request to Bluefin + Inputs: + symbol (MARKET_SYMBOLS): Market for which orders are to be cancelled + status (List[ORDER_STATUS]): status of orders that need to be cancelled + parentAddress (str): address of parent account, only provided by sub account + Returns: + dict: response from orders delete API Bluefin + """ + orders = await self.get_orders( + {"symbol": symbol, "parentAddress": parentAddress, "statuses": status} + ) hashes = [] for i in orders: hashes.append(i["hash"]) - + if len(hashes) > 0: req = self.create_signed_cancel_orders(symbol, hashes, parentAddress) return await self.post_cancel_order(req) return False - - async def post_signed_order(self, params:PlaceOrderRequest): + + async def post_signed_order(self, params: PlaceOrderRequest): """ - Creates an order from provided params and signs it using the private - key of the account + Creates an order from provided params and signs it using the private + key of the account - Inputs: - params (OrderSignatureRequest): parameters to create order with + Inputs: + params (OrderSignatureRequest): parameters to create order with - Returns: - OrderSignatureResponse: order raw info and generated signature + Returns: + OrderSignatureResponse: order raw info and generated signature """ return await self.apis.post( SERVICE_URLS["ORDERS"]["ORDERS"], { - "orderbookOnly": params["orderbookOnly"], - "symbol": params["symbol"], - "price": params["price"], - "quantity": params["quantity"], - "leverage": params["leverage"], - "userAddress": params["maker"], - "orderType": params["orderType"].value, - "side": params["side"].value, - "reduceOnly": params["reduceOnly"], - "salt": params["salt"], - "expiration": params["expiration"], - "orderSignature": params["orderSignature"], - "timeInForce": default_enum_value(params, "timeInForce", TIME_IN_FORCE.GOOD_TILL_TIME), - "postOnly": default_value(params, "postOnly", False), - "cancelOnRevert": default_value(params, "cancelOnRevert", False), - "clientId": "firefly-client: {}".format(default_value(params, "clientId", "firefly-client")) + "orderbookOnly": params["orderbookOnly"], + "symbol": params["symbol"], + "price": params["price"], + "quantity": params["quantity"], + "leverage": params["leverage"], + "userAddress": params["maker"], + "orderType": params["orderType"].value, + "side": params["side"].value, + "reduceOnly": params["reduceOnly"], + "salt": params["salt"], + "expiration": params["expiration"], + "orderSignature": params["orderSignature"], + "timeInForce": default_enum_value( + params, "timeInForce", TIME_IN_FORCE.GOOD_TILL_TIME + ), + "postOnly": default_value(params, "postOnly", False), + "cancelOnRevert": default_value(params, "cancelOnRevert", False), + "clientId": "bluefin-python-client: {}".format( + default_value(params, "clientId", "bluefin-python-client") + ), }, - auth_required=True - ) + auth_required=True, + ) ## Contract calls async def deposit_margin_to_bank(self, amount: int, coin_id: str) -> bool: """ - Deposits given amount of USDC from user's account to margin bank + Deposits given amount of USDC from user's account to margin bank - Inputs: - amount (number): quantity of usdc to be deposited to bank in base decimals (1,2 etc) + Inputs: + amount (number): quantity of usdc to be deposited to bank in base decimals (1,2 etc) - Returns: - Boolean: true if amount is successfully deposited, false otherwise + Returns: + Boolean: true if amount is successfully deposited, false otherwise """ - package_id=self.contracts.get_package_id() - user_address=self.account.getUserAddress() - callArgs=[] + package_id = self.contracts.get_package_id() + user_address = self.account.getUserAddress() + callArgs = [] callArgs.append(self.contracts.get_bank_id()) callArgs.append(self.account.getUserAddress()) callArgs.append(str(self._to_sui_base(amount))) callArgs.append(coin_id) - txBytes=rpc_unsafe_moveCall(self.url, - callArgs, - "deposit_to_bank", - "margin_bank", - user_address, - package_id) - signature=self.contract_signer.sign_tx(txBytes, self.account) - res=rpc_sui_executeTransactionBlock(self.url, - txBytes, - signature) - + txBytes = rpc_unsafe_moveCall( + self.url, + callArgs, + "deposit_to_bank", + "margin_bank", + user_address, + package_id, + ) + signature = self.contract_signer.sign_tx(txBytes, self.account) + res = rpc_sui_executeTransactionBlock(self.url, txBytes, signature) return res async def withdraw_margin_from_bank(self, amount): """ - Withdraws given amount of usdc from margin bank if possible + Withdraws given amount of usdc from margin bank if possible - Inputs: - amount (number): quantity of usdc to be withdrawn from bank in base decimals (1,2 etc) + Inputs: + amount (number): quantity of usdc to be withdrawn from bank in base decimals (1,2 etc) - Returns: - Boolean: true if amount is successfully withdrawn, false otherwise + Returns: + Boolean: true if amount is successfully withdrawn, false otherwise """ - bank_id=self.contracts.get_bank_id() - account_address=self.account.getUserAddress() + bank_id = self.contracts.get_bank_id() + account_address = self.account.getUserAddress() - callArgs=[bank_id, account_address, str(amount)] - txBytes=rpc_unsafe_moveCall(self.url, - callArgs, - "withdraw_from_bank", - "margin_bank", - self.account.getUserAddress(), - self.contracts.get_package_id() - ) - signature=self.contract_signer.sign_tx(txBytes, self.account) - res=rpc_sui_executeTransactionBlock(self.url, - txBytes, - signature) + callArgs = [bank_id, account_address, str(amount)] + txBytes = rpc_unsafe_moveCall( + self.url, + callArgs, + "withdraw_from_bank", + "margin_bank", + self.account.getUserAddress(), + self.contracts.get_package_id(), + ) + signature = self.contract_signer.sign_tx(txBytes, self.account) + res = rpc_sui_executeTransactionBlock(self.url, txBytes, signature) return res async def withdraw_all_margin_from_bank(self): - bank_id=self.contracts.get_bank_id() - account_address=self.account.getUserAddress() + bank_id = self.contracts.get_bank_id() + account_address = self.account.getUserAddress() - callArgs=[bank_id, account_address] - txBytes=rpc_unsafe_moveCall(self.url, + callArgs = [bank_id, account_address] + txBytes = rpc_unsafe_moveCall( + self.url, callArgs, "withdraw_all_margin_from_bank", "margin_bank", self.account.getUserAddress(), - self.contracts.get_package_id() - ) - signature=self.contract_signer.sign_tx(txBytes, self.account) - res=rpc_sui_executeTransactionBlock(self.url, - txBytes, - signature + self.contracts.get_package_id(), ) + signature = self.contract_signer.sign_tx(txBytes, self.account) + res = rpc_sui_executeTransactionBlock(self.url, txBytes, signature) return res - - async def adjust_leverage(self, symbol, leverage, parentAddress:str=""): + async def adjust_leverage(self, symbol, leverage, parentAddress: str = ""): """ - Adjusts user leverage to the provided one for their current position on-chain and off-chain. - If a user has no position for the provided symbol, leverage only recorded off-chain + Adjusts user leverage to the provided one for their current position on-chain and off-chain. + If a user has no position for the provided symbol, leverage only recorded off-chain - Inputs: - symbol (MARKET_SYMBOLS): market for which to adjust user leverage - leverage (number): new leverage to be set. Must be in base decimals (1,2 etc.) - parentAddress (str): optional, if provided, the leverage of parent is - being adjusted (for sub accounts only) - Returns: - Boolean: true if the leverage is successfully adjusted + Inputs: + symbol (MARKET_SYMBOLS): market for which to adjust user leverage + leverage (number): new leverage to be set. Must be in base decimals (1,2 etc.) + parentAddress (str): optional, if provided, the leverage of parent is + being adjusted (for sub accounts only) + Returns: + Boolean: true if the leverage is successfully adjusted """ - user_position = await self.get_user_position({"symbol":symbol, "parentAddress": parentAddress}) - - account_address = self.account.address if parentAddress=="" else parentAddress + user_position = await self.get_user_position( + {"symbol": symbol, "parentAddress": parentAddress} + ) + + account_address = self.account.address if parentAddress == "" else parentAddress # implies user has an open position on-chain, perform on-chain leverage update - if(user_position != {}): - callArgs = []; + if user_position != {}: + callArgs = [] callArgs.append(self.contracts.get_perpetual_id(symbol)) callArgs.append(self.contracts.get_bank_id()) callArgs.append(self.contracts.get_sub_account_id()) callArgs.append(account_address) callArgs.append(str(self._to_sui_base(leverage))) callArgs.append(self.contracts.get_price_oracle_object_id(symbol.value)) - txBytes=rpc_unsafe_moveCall(self.url, + txBytes = rpc_unsafe_moveCall( + self.url, callArgs, "adjust_leverage", "exchange", self.account.getUserAddress(), - self.contracts.get_package_id() + self.contracts.get_package_id(), ) - signature=self.contract_signer.sign_tx(txBytes, self.account) - result=rpc_sui_executeTransactionBlock(self.url, txBytes, signature) + signature = self.contract_signer.sign_tx(txBytes, self.account) + result = rpc_sui_executeTransactionBlock(self.url, txBytes, signature) return result else: await self.apis.post( @@ -414,29 +439,36 @@ async def adjust_leverage(self, symbol, leverage, parentAddress:str=""): "leverage": toDapiBase(leverage), "marginType": MARGIN_TYPE.ISOLATED.value, }, - auth_required=True + auth_required=True, ) return True - async def adjust_margin(self, symbol: MARKET_SYMBOLS, operation: ADJUST_MARGIN, amount: str, parentAddress:str=""): + async def adjust_margin( + self, + symbol: MARKET_SYMBOLS, + operation: ADJUST_MARGIN, + amount: str, + parentAddress: str = "", + ): """ - Adjusts user's on-chain position by adding or removing the specified amount of margin. - Performs on-chain contract call, the user must have gas tokens - Inputs: - symbol (MARKET_SYMBOLS): market for which to adjust user leverage - operation (ADJUST_MARGIN): ADD/REMOVE adding or removing margin to position - amount (number): amount of margin to be adjusted - parentAddress (str): optional, if provided, the margin of parent is - being adjusted (for sub accounts only) - Returns: - Boolean: true if the margin is adjusted + Adjusts user's on-chain position by adding or removing the specified amount of margin. + Performs on-chain contract call, the user must have gas tokens + Inputs: + symbol (MARKET_SYMBOLS): market for which to adjust user leverage + operation (ADJUST_MARGIN): ADD/REMOVE adding or removing margin to position + amount (number): amount of margin to be adjusted + parentAddress (str): optional, if provided, the margin of parent is + being adjusted (for sub accounts only) + Returns: + Boolean: true if the margin is adjusted """ - user_position = await self.get_user_position({"symbol":symbol, "parentAddress": parentAddress}) + user_position = await self.get_user_position( + {"symbol": symbol, "parentAddress": parentAddress} + ) - - if(user_position == {}): - raise(Exception(f"User has no open position on market: {symbol}")) + if user_position == {}: + raise (Exception(f"User has no open position on market: {symbol}")) callArgs = [] callArgs.append(self.contracts.get_perpetual_id(symbol)) @@ -446,310 +478,286 @@ async def adjust_margin(self, symbol: MARKET_SYMBOLS, operation: ADJUST_MARGIN, callArgs.append(self.account.getUserAddress()) callArgs.append(str(amount)) callArgs.append(self.contracts.get_price_oracle_object_id(symbol)) - if operation==ADJUST_MARGIN.ADD: - txBytes=rpc_unsafe_moveCall(self.url, + if operation == ADJUST_MARGIN.ADD: + txBytes = rpc_unsafe_moveCall( + self.url, callArgs, "add_margin", "exchange", self.account.getUserAddress(), - self.contracts.get_package_id() + self.contracts.get_package_id(), ) - + else: - txBytes=rpc_unsafe_moveCall(self.url, + txBytes = rpc_unsafe_moveCall( + self.url, callArgs, "remove_margin", "exchange", self.account.getUserAddress(), - self.contracts.get_package_id() + self.contracts.get_package_id(), ) - - signature=self.contract_signer.sign_tx(txBytes, self.account) - result=rpc_sui_executeTransactionBlock(self.url,txBytes, signature) + + signature = self.contract_signer.sign_tx(txBytes, self.account) + result = rpc_sui_executeTransactionBlock(self.url, txBytes, signature) return True - - + async def update_sub_account(self, sub_account_address: str, status: bool) -> bool: """ - Used to whitelist and account as a sub account or revoke sub account status from an account. - Inputs: - sub_account_address (str): address of the sub account - status (bool): new status of the sub account + Used to whitelist and account as a sub account or revoke sub account status from an account. + Inputs: + sub_account_address (str): address of the sub account + status (bool): new status of the sub account - Returns: - Boolean: true if the sub account status is update + Returns: + Boolean: true if the sub account status is update """ - callArgs=[] + callArgs = [] callArgs.append(self.contracts.get_sub_account_id()) callArgs.append(sub_account_address) callArgs.append(status) - txBytes=rpc_unsafe_moveCall(self.url, + txBytes = rpc_unsafe_moveCall( + self.url, callArgs, "set_sub_account", - "roles", + "roles", self.account.getUserAddress(), - self.contracts.get_package_id() + self.contracts.get_package_id(), ) - - signature=self.contract_signer.sign_tx(txBytes, self.account) - result=rpc_sui_executeTransactionBlock(self.url, txBytes, signature) - if result['result']['effects']['status']['status']=='success': + + signature = self.contract_signer.sign_tx(txBytes, self.account) + result = rpc_sui_executeTransactionBlock(self.url, txBytes, signature) + if result["result"]["effects"]["status"]["status"] == "success": return True else: return False - async def get_native_chain_token_balance(self)-> float: + async def get_native_chain_token_balance(self) -> float: """ - Returns user's native chain token (SUI) balance + Returns user's native chain token (SUI) balance """ try: - callArgs=[] + callArgs = [] callArgs.append(self.account.getUserAddress()) callArgs.append("0x2::sui::SUI") - result=rpc_call_sui_function(self.url, callArgs, method="suix_getBalance")["totalBalance"] + result = rpc_call_sui_function( + self.url, callArgs, method="suix_getBalance" + )["totalBalance"] return self._from_sui_base(result) except Exception as e: - raise(Exception(f"Failed to get balance, error: {e}")) - + raise (Exception(f"Failed to get balance, error: {e}")) def get_usdc_coins(self): try: - callArgs=[] + callArgs = [] callArgs.append(self.account.getUserAddress()) callArgs.append(self.contracts.get_currency_type()) - result=rpc_call_sui_function(self.url,callArgs, method="suix_getCoins" ) + result = rpc_call_sui_function(self.url, callArgs, method="suix_getCoins") return result except Exception as e: - raise(Exception("Failed to get USDC coins, Exception: {}".format(e))) + raise (Exception("Failed to get USDC coins, Exception: {}".format(e))) - async def get_usdc_balance(self)-> float: + async def get_usdc_balance(self) -> float: """ - Returns user's USDC token balance on Firefly. + Returns user's USDC token balance on Bluefin. """ try: - callArgs=[] + callArgs = [] callArgs.append(self.account.getUserAddress()) callArgs.append(self.contracts.get_currency_type()) - result=rpc_call_sui_function(self.url, callArgs, method="suix_getBalance")["totalBalance"] + result = rpc_call_sui_function( + self.url, callArgs, method="suix_getBalance" + )["totalBalance"] return self._from_sui_base(result) - + except Exception as e: - raise(Exception("Failed to get balance, Exception: {}".format(e))) + raise (Exception("Failed to get balance, Exception: {}".format(e))) - async def get_margin_bank_balance(self)-> float: + async def get_margin_bank_balance(self) -> float: """ - Returns user's Margin Bank balance. + Returns user's Margin Bank balance. """ try: - call_args=[] + call_args = [] call_args.append(self.contracts.get_bank_table_id()) - call_args.append({ - "type": "address", - "value": self.account.getUserAddress() - }) - result=rpc_call_sui_function(self.url, call_args, method="suix_getDynamicFieldObject") - - balance=self._from_sui_base(result["data"]["content"]["fields"]["value"]["fields"]["balance"]) + call_args.append( + {"type": "address", "value": self.account.getUserAddress()} + ) + result = rpc_call_sui_function( + self.url, call_args, method="suix_getDynamicFieldObject" + ) + + balance = self._from_sui_base( + result["data"]["content"]["fields"]["value"]["fields"]["balance"] + ) return balance except Exception as e: - raise(Exception("Failed to get balance, Exception: {}".format(e))) + raise (Exception("Failed to get balance, Exception: {}".format(e))) ## Market endpoints - async def get_orderbook(self, params:GetOrderbookRequest): + async def get_orderbook(self, params: GetOrderbookRequest): """ - Returns a dictionary containing the orderbook snapshot. - Inputs: - params(GetOrderbookRequest): the order symbol and limit(orderbook depth) - Returns: - dict: Orderbook snapshot + Returns a dictionary containing the orderbook snapshot. + Inputs: + params(GetOrderbookRequest): the order symbol and limit(orderbook depth) + Returns: + dict: Orderbook snapshot """ params = extract_enums(params, ["symbol"]) - return await self.apis.get( - SERVICE_URLS["MARKET"]["ORDER_BOOK"], - params - ) + return await self.apis.get(SERVICE_URLS["MARKET"]["ORDER_BOOK"], params) async def get_exchange_status(self): """ - Returns a dictionary containing the exchange status. - Returns: - dict: exchange status + Returns a dictionary containing the exchange status. + Returns: + dict: exchange status """ return await self.apis.get(SERVICE_URLS["MARKET"]["STATUS"], {}) async def get_market_symbols(self): """ - Returns a list of active market symbols. - Returns: - list: active market symbols + Returns a list of active market symbols. + Returns: + list: active market symbols """ - return await self.apis.get( - SERVICE_URLS["MARKET"]["SYMBOLS"], - {} - ) + return await self.apis.get(SERVICE_URLS["MARKET"]["SYMBOLS"], {}) - async def get_funding_rate(self,symbol:MARKET_SYMBOLS): + async def get_funding_rate(self, symbol: MARKET_SYMBOLS): """ - Returns a dictionary containing the current funding rate on market. - Inputs: - symbol(MARKET_SYMBOLS): symbol of market - Returns: - dict: Funding rate into + Returns a dictionary containing the current funding rate on market. + Inputs: + symbol(MARKET_SYMBOLS): symbol of market + Returns: + dict: Funding rate into """ return await self.apis.get( - SERVICE_URLS["MARKET"]["FUNDING_RATE"], - {"symbol": symbol.value} + SERVICE_URLS["MARKET"]["FUNDING_RATE"], {"symbol": symbol.value} ) - - async def get_transfer_history(self,params:GetTransferHistoryRequest): - """ - Returns a list of the user's transfer history records, a boolean indicating if there is/are more page(s), - and the next page number - Inputs: - params(GetTransferHistoryRequest): params required to fetch transfer history - Returns: - GetUserTransferHistoryResponse: - isMoreDataAvailable: boolean indicating if there is/are more page(s) - nextCursor: the next page number - data: a list of the user's transfer history record - """ - + + async def get_transfer_history(self, params: GetTransferHistoryRequest): + """ + Returns a list of the user's transfer history records, a boolean indicating if there is/are more page(s), + and the next page number + Inputs: + params(GetTransferHistoryRequest): params required to fetch transfer history + Returns: + GetUserTransferHistoryResponse: + isMoreDataAvailable: boolean indicating if there is/are more page(s) + nextCursor: the next page number + data: a list of the user's transfer history record + """ + return await self.apis.get( - SERVICE_URLS["USER"]["TRANSFER_HISTORY"], - params, - auth_required=True + SERVICE_URLS["USER"]["TRANSFER_HISTORY"], params, auth_required=True ) - async def get_funding_history(self,params:GetFundingHistoryRequest): + async def get_funding_history(self, params: GetFundingHistoryRequest): """ - Returns a list of the user's funding payments, a boolean indicating if there is/are more page(s), - and the next page number - Inputs: - params(GetFundingHistoryRequest): params required to fetch funding history - Returns: - GetFundingHistoryResponse: - isMoreDataAvailable: boolean indicating if there is/are more page(s) - nextCursor: the next page number - data: a list of the user's funding payments + Returns a list of the user's funding payments, a boolean indicating if there is/are more page(s), + and the next page number + Inputs: + params(GetFundingHistoryRequest): params required to fetch funding history + Returns: + GetFundingHistoryResponse: + isMoreDataAvailable: boolean indicating if there is/are more page(s) + nextCursor: the next page number + data: a list of the user's funding payments """ - params = extract_enums(params,["symbol"]) - + params = extract_enums(params, ["symbol"]) + return await self.apis.get( - SERVICE_URLS["USER"]["FUNDING_HISTORY"], - params, - auth_required=True + SERVICE_URLS["USER"]["FUNDING_HISTORY"], params, auth_required=True ) - async def get_market_meta_info(self,symbol:MARKET_SYMBOLS=None): + async def get_market_meta_info(self, symbol: MARKET_SYMBOLS = None): """ - Returns a dictionary containing market meta info. - Inputs: - symbol(MARKET_SYMBOLS): the market symbol - Returns: - dict: meta info + Returns a dictionary containing market meta info. + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: meta info """ - query = {"symbol": symbol.value } if symbol else {} + query = {"symbol": symbol.value} if symbol else {} - return await self.apis.get( - SERVICE_URLS["MARKET"]["META"], - query - ) + return await self.apis.get(SERVICE_URLS["MARKET"]["META"], query) - async def get_market_data(self,symbol:MARKET_SYMBOLS=None): + async def get_market_data(self, symbol: MARKET_SYMBOLS = None): """ - Returns a dictionary containing market's current data about best ask/bid, 24 hour volume, market price etc.. - Inputs: - symbol(MARKET_SYMBOLS): the market symbol - Returns: - dict: meta info + Returns a dictionary containing market's current data about best ask/bid, 24 hour volume, market price etc.. + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: meta info """ - query = {"symbol": symbol.value } if symbol else {} + query = {"symbol": symbol.value} if symbol else {} - return await self.apis.get( - SERVICE_URLS["MARKET"]["MARKET_DATA"], - query - ) - - async def get_exchange_info(self,symbol:MARKET_SYMBOLS=None): + return await self.apis.get(SERVICE_URLS["MARKET"]["MARKET_DATA"], query) + + async def get_exchange_info(self, symbol: MARKET_SYMBOLS = None): """ - Returns a dictionary containing exchange info for market(s). The min/max trade size, max allowed oi open - min/max trade price, step size, tick size etc... - Inputs: - symbol(MARKET_SYMBOLS): the market symbol - Returns: - dict: exchange info + Returns a dictionary containing exchange info for market(s). The min/max trade size, max allowed oi open + min/max trade price, step size, tick size etc... + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: exchange info """ - query = {"symbol": symbol.value } if symbol else {} - return await self.apis.get( - SERVICE_URLS["MARKET"]["EXCHANGE_INFO"], - query - ) - - async def get_master_info(self,symbol:MARKET_SYMBOLS=None): + query = {"symbol": symbol.value} if symbol else {} + return await self.apis.get(SERVICE_URLS["MARKET"]["EXCHANGE_INFO"], query) + + async def get_master_info(self, symbol: MARKET_SYMBOLS = None): """ - Returns a dictionary containing master info for market(s). - It contains all market data, exchange info and meta data of market(s) - Inputs: - symbol(MARKET_SYMBOLS): the market symbol - Returns: - dict: master info + Returns a dictionary containing master info for market(s). + It contains all market data, exchange info and meta data of market(s) + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: master info """ - query = {"symbol": symbol.value } if symbol else {} - return await self.apis.get( - SERVICE_URLS["MARKET"]["MASTER_INFO"], - query - ) - - async def get_ticker_data(self,symbol:MARKET_SYMBOLS=None): + query = {"symbol": symbol.value} if symbol else {} + return await self.apis.get(SERVICE_URLS["MARKET"]["MASTER_INFO"], query) + + async def get_ticker_data(self, symbol: MARKET_SYMBOLS = None): """ - Returns a dictionary containing ticker data for market(s). - Inputs: - symbol(MARKET_SYMBOLS): the market symbol - Returns: - dict: ticker info + Returns a dictionary containing ticker data for market(s). + Inputs: + symbol(MARKET_SYMBOLS): the market symbol + Returns: + dict: ticker info """ - query = {"symbol": symbol.value } if symbol else {} - return await self.apis.get( - SERVICE_URLS["MARKET"]["TICKER"], - query - ) + query = {"symbol": symbol.value} if symbol else {} + return await self.apis.get(SERVICE_URLS["MARKET"]["TICKER"], query) - async def get_market_candle_stick_data(self,params:GetCandleStickRequest): + async def get_market_candle_stick_data(self, params: GetCandleStickRequest): """ - Returns a list containing the candle stick data. - Inputs: - params(GetCandleStickRequest): params required to fetch candle stick data - Returns: - list: the candle stick data + Returns a list containing the candle stick data. + Inputs: + params(GetCandleStickRequest): params required to fetch candle stick data + Returns: + list: the candle stick data """ - params = extract_enums(params, ["symbol","interval"]) - - return await self.apis.get( - SERVICE_URLS["MARKET"]["CANDLE_STICK_DATA"], - params - ) - - async def get_market_recent_trades(self,params:GetMarketRecentTradesRequest): + params = extract_enums(params, ["symbol", "interval"]) + + return await self.apis.get(SERVICE_URLS["MARKET"]["CANDLE_STICK_DATA"], params) + + async def get_market_recent_trades(self, params: GetMarketRecentTradesRequest): """ - Returns a list containing the recent trades data. - Inputs: - params(GetCandleStickRequest): params required to fetch candle stick data - Returns: - ist: the recent trades + Returns a list containing the recent trades data. + Inputs: + params(GetCandleStickRequest): params required to fetch candle stick data + Returns: + ist: the recent trades """ params = extract_enums(params, ["symbol", "traders"]) - return await self.apis.get( - SERVICE_URLS["MARKET"]["RECENT_TRADE"], - params - ) + return await self.apis.get(SERVICE_URLS["MARKET"]["RECENT_TRADE"], params) async def get_contract_addresses(self): """ - Returns: - dict: all contract addresses for the all markets. + Returns: + dict: all contract addresses for the all markets. """ return await self.apis.get(SERVICE_URLS["MARKET"]["CONTRACT_ADDRESSES"]) @@ -757,173 +765,160 @@ async def get_contract_addresses(self): def get_account(self): """ - Returns the user account object + Returns the user account object """ return self.account def get_public_address(self): """ - Returns the user account public address + Returns the user account public address """ return self.account.address async def generate_readonly_token(self): """ - Returns a read-only token generated for authenticated user. + Returns a read-only token generated for authenticated user. """ return await self.apis.post( - SERVICE_URLS["USER"]["GENERATE_READONLY_TOKEN"], - {}, - True + SERVICE_URLS["USER"]["GENERATE_READONLY_TOKEN"], {}, True ) - async def get_orders(self,params:GetOrderRequest): + async def get_orders(self, params: GetOrderRequest): """ - Returns a list of orders. - Inputs: - params(GetOrderRequest): params required to query orders (e.g. symbol,statuses) - Returns: - list: a list of orders + Returns a list of orders. + Inputs: + params(GetOrderRequest): params required to query orders (e.g. symbol,statuses) + Returns: + list: a list of orders """ - params = extract_enums(params,["symbol","statuses", "orderType"]) + params = extract_enums(params, ["symbol", "statuses", "orderType"]) - return await self.apis.get( - SERVICE_URLS["USER"]["ORDERS"], - params, - True - ) - - async def get_transaction_history(self,params:GetTransactionHistoryRequest): + return await self.apis.get(SERVICE_URLS["USER"]["ORDERS"], params, True) + + async def get_transaction_history(self, params: GetTransactionHistoryRequest): """ - Returns a list of transaction. - Inputs: - params(GetTransactionHistoryRequest): params to query transactions (e.g. symbol) - Returns: - list: a list of transactions + Returns a list of transaction. + Inputs: + params(GetTransactionHistoryRequest): params to query transactions (e.g. symbol) + Returns: + list: a list of transactions """ - params = extract_enums(params,["symbol"]) + params = extract_enums(params, ["symbol"]) return await self.apis.get( - SERVICE_URLS["USER"]["USER_TRANSACTION_HISTORY"], - params, - True + SERVICE_URLS["USER"]["USER_TRANSACTION_HISTORY"], params, True ) - - async def get_user_position(self,params:GetPositionRequest): + + async def get_user_position(self, params: GetPositionRequest): """ - Returns a list of positions. - Inputs: - params(GetPositionRequest): params required to query positions (e.g. symbol) - Returns: - list: a list of positions + Returns a list of positions. + Inputs: + params(GetPositionRequest): params required to query positions (e.g. symbol) + Returns: + list: a list of positions """ - params = extract_enums(params,["symbol"]) - return await self.apis.get( - SERVICE_URLS["USER"]["USER_POSITIONS"], - params, - True - ) + params = extract_enums(params, ["symbol"]) + return await self.apis.get(SERVICE_URLS["USER"]["USER_POSITIONS"], params, True) - async def get_user_trades(self,params:GetUserTradesRequest): + async def get_user_trades(self, params: GetUserTradesRequest): """ - Returns a list of user trades. - Inputs: - params(GetUserTradesRequest): params to query trades (e.g. symbol) - Returns: - list: a list of positions + Returns a list of user trades. + Inputs: + params(GetUserTradesRequest): params to query trades (e.g. symbol) + Returns: + list: a list of positions """ - params = extract_enums(params,["symbol","type"]) - return await self.apis.get( - SERVICE_URLS["USER"]["USER_TRADES"], - params, - True - ) + params = extract_enums(params, ["symbol", "type"]) + return await self.apis.get(SERVICE_URLS["USER"]["USER_TRADES"], params, True) - async def get_user_account_data(self, parentAddress:str = ""): + async def get_user_account_data(self, parentAddress: str = ""): """ - Returns user account data. - Inputs: - parentAddress: an optional field, used by sub accounts to fetch parent account state + Returns user account data. + Inputs: + parentAddress: an optional field, used by sub accounts to fetch parent account state """ return await self.apis.get( - service_url = SERVICE_URLS["USER"]["ACCOUNT"], - query = { "parentAddress": parentAddress }, - auth_required = True + service_url=SERVICE_URLS["USER"]["ACCOUNT"], + query={"parentAddress": parentAddress}, + auth_required=True, ) - - async def get_user_leverage(self, symbol:MARKET_SYMBOLS, parentAddress:str=""): - """ - Returns user market default leverage. - Inputs: - symbol(MARKET_SYMBOLS): market symbol to get user market default leverage for. - parentAddress(str): an optional field, used by sub accounts to fetch parent account state - Returns: - str: user default leverage - """ - account_data_by_market = (await self.get_user_account_data(parentAddress))["accountDataByMarket"] - + + async def get_user_leverage(self, symbol: MARKET_SYMBOLS, parentAddress: str = ""): + """ + Returns user market default leverage. + Inputs: + symbol(MARKET_SYMBOLS): market symbol to get user market default leverage for. + parentAddress(str): an optional field, used by sub accounts to fetch parent account state + Returns: + str: user default leverage + """ + account_data_by_market = (await self.get_user_account_data(parentAddress))[ + "accountDataByMarket" + ] + for i in account_data_by_market: - if symbol.value==i["symbol"]: + if symbol.value == i["symbol"]: return fromDapiBase(int(i["selectedLeverage"])) # default leverage on system is 3 # todo fetch from exchange info route return 3 - async def get_cancel_on_disconnect_timer(self, params:GetCancelOnDisconnectTimerRequest=None): + async def get_cancel_on_disconnect_timer( + self, params: GetCancelOnDisconnectTimerRequest = None + ): """ - Returns a list of the user's countDowns for provided market symbol, - Inputs: - - symbol(MARKET_SYMBOLS): (Optional) market symbol to get user market cancel_on_disconnect timer for, not providing it would return all the active countDown timers for each market. - - parentAddress (str):(Optional) Only provided by a sub account - Returns: - - GetCountDownsResponse: - - countDowns: object with provided market symbol and respective countDown timer - - timestamp + Returns a list of the user's countDowns for provided market symbol, + Inputs: + - symbol(MARKET_SYMBOLS): (Optional) market symbol to get user market cancel_on_disconnect timer for, not providing it would return all the active countDown timers for each market. + - parentAddress (str):(Optional) Only provided by a sub account + Returns: + - GetCountDownsResponse: + - countDowns: object with provided market symbol and respective countDown timer + - timestamp """ - + params = extract_enums(params, ["symbol"]) response = await self.dms_api.get( - SERVICE_URLS["USER"]["CANCEL_ON_DISCONNECT"], - params, - auth_required=True + SERVICE_URLS["USER"]["CANCEL_ON_DISCONNECT"], params, auth_required=True ) # check for service unavailibility - if hasattr(response, 'status') and response.status == 503: - raise Exception("Cancel on Disconnect (dead-mans-switch) feature is currently unavailable") - + if hasattr(response, "status") and response.status == 503: + raise Exception( + "Cancel on Disconnect (dead-mans-switch) feature is currently unavailable" + ) + return response - - async def reset_cancel_on_disconnect_timer(self, params:PostTimerAttributes): + + async def reset_cancel_on_disconnect_timer(self, params: PostTimerAttributes): """ - Returns PostTimerResponse containing accepted and failed countdowns, and the next page number - Inputs: - - params(PostTimerAttributes): params required to fetch funding history - Returns: - - PostTimerResponse: - - acceptedToReset: array with symbols for which timer was reset successfully - - failedReset: aray with symbols for whcih timer failed to reset + Returns PostTimerResponse containing accepted and failed countdowns, and the next page number + Inputs: + - params(PostTimerAttributes): params required to fetch funding history + Returns: + - PostTimerResponse: + - acceptedToReset: array with symbols for which timer was reset successfully + - failedReset: aray with symbols for whcih timer failed to reset """ response = await self.dms_api.post( SERVICE_URLS["USER"]["CANCEL_ON_DISCONNECT"], json.dumps(params), auth_required=True, - contentType = 'application/json' + contentType="application/json", ) # check for service unavailibility - if hasattr(response, 'status') and response.status == 503: - raise Exception("Cancel on Disconnect (dead-mans-switch) feature is currently unavailable") + if hasattr(response, "status") and response.status == 503: + raise Exception( + "Cancel on Disconnect (dead-mans-switch) feature is currently unavailable" + ) return response - async def close_connections(self): # close aio http connection await self.apis.close_session() await self.dms_api.close_session() + def _from_sui_base(self, number: Union[str, int]) -> float: + number = float(number) + return number / float(_SUI_BASE_NUM) - def _from_sui_base(self, number: Union[str,int]) -> float: - number=float(number) - return number/float(_SUI_BASE_NUM) - - - def _to_sui_base(self, number: Union[int,float]) -> int: - return int(number*_SUI_BASE_NUM) + def _to_sui_base(self, number: Union[int, float]) -> int: + return int(number * _SUI_BASE_NUM) diff --git a/src/bluefin_client_sui/constants.py b/src/bluefin_client_sui/constants.py index fe91da3..af29f9b 100644 --- a/src/bluefin_client_sui/constants.py +++ b/src/bluefin_client_sui/constants.py @@ -1,70 +1,67 @@ Networks = { - "SUI_STAGING":{ - "url":"https://fullnode.testnet.sui.io:443", - "apiGateway":"https://dapi.api.sui-staging.bluefin.io", - "socketURL":"wss://dapi.api.sui-staging.bluefin.io", - "dmsURL":"https://dapi.api.sui-staging.bluefin.io", - "webSocketURL":"wss://notifications.api.sui-staging.bluefin.io", - "onboardingUrl": "https://testnet.bluefin.io" - }, - "SUI_PROD":{ - "url":"https://fullnode.testnet.sui.io:443", - "apiGateway":"https://dapi.api.sui-prod.bluefin.io", - "socketURL":"wss://dapi.api.sui-prod.bluefin.io", - "dmsURL":"https://dapi.api.sui-prod.bluefin.io", - "webSocketURL":"wss://notifications.api.sui-prod.bluefin.io", - "onboardingUrl": "https://trade.bluefin.io" - } + "SUI_STAGING": { + "url": "https://fullnode.testnet.sui.io:443", + "apiGateway": "https://dapi.api.sui-staging.bluefin.io", + "socketURL": "wss://dapi.api.sui-staging.bluefin.io", + "dmsURL": "https://dapi.api.sui-staging.bluefin.io", + "webSocketURL": "wss://notifications.api.sui-staging.bluefin.io", + "onboardingUrl": "https://testnet.bluefin.io", + }, + "SUI_PROD": { + "url": "https://fullnode.testnet.sui.io:443", + "apiGateway": "https://dapi.api.sui-prod.bluefin.io", + "socketURL": "wss://dapi.api.sui-prod.bluefin.io", + "dmsURL": "https://dapi.api.sui-prod.bluefin.io", + "webSocketURL": "wss://notifications.api.sui-prod.bluefin.io", + "onboardingUrl": "https://trade.bluefin.io", + }, } -ORDER_FLAGS = { - "IS_BUY":1, - "IS_DECREASE_ONLY": 2 -} +ORDER_FLAGS = {"IS_BUY": 1, "IS_DECREASE_ONLY": 2} TIME = { - "SECONDS_IN_A_MINUTE": 60, - "SECONDS_IN_A_DAY": 86400, - "SECONDS_IN_A_MONTH": 2592000 + "SECONDS_IN_A_MINUTE": 60, + "SECONDS_IN_A_DAY": 86400, + "SECONDS_IN_A_MONTH": 2592000, } ADDRESSES = { - "ZERO": "0x0000000000000000000000000000000000000000", + "ZERO": "0x0000000000000000000000000000000000000000", } SERVICE_URLS = { - "MARKET": { - "ORDER_BOOK": "/orderbook", - "RECENT_TRADE": "/recentTrades", - "CANDLE_STICK_DATA": "/candlestickData", - "EXCHANGE_INFO": "/exchangeInfo", - "MARKET_DATA": "/marketData", - "META": "/meta", - "STATUS": "/status", - "SYMBOLS": "/marketData/symbols", - "CONTRACT_ADDRESSES": "/marketData/contractAddresses", - "TICKER": "/ticker", - "MASTER_INFO": "/masterInfo", - "FUNDING_RATE":"/fundingRate" - }, - "USER": { - "USER_POSITIONS": "/userPosition", - "USER_TRADES": "/userTrades", - "ORDERS": "/orders", - "GENERATE_READONLY_TOKEN": "/generateReadOnlyToken", - "ACCOUNT": "/account", - "USER_TRANSACTION_HISTORY": "/userTransactionHistory", - "AUTHORIZE": "/authorize", - "ADJUST_LEVERAGE": "/account/adjustLeverage", - "FUND_GAS": "/account/fundGas", - "TRANSFER_HISTORY": "/userTransferHistory", - "FUNDING_HISTORY": "/userFundingHistory", - "CANCEL_ON_DISCONNECT": "/dms-countdown" - }, - "ORDERS": { - "ORDERS": "/orders", - "ORDERS_HASH": "/orders/hash", - }, + "MARKET": { + "ORDER_BOOK": "/orderbook", + "RECENT_TRADE": "/recentTrades", + "CANDLE_STICK_DATA": "/candlestickData", + "EXCHANGE_INFO": "/exchangeInfo", + "MARKET_DATA": "/marketData", + "META": "/meta", + "STATUS": "/status", + "SYMBOLS": "/marketData/symbols", + "CONTRACT_ADDRESSES": "/marketData/contractAddresses", + "TICKER": "/ticker", + "MASTER_INFO": "/masterInfo", + "FUNDING_RATE": "/fundingRate", + }, + "USER": { + "USER_POSITIONS": "/userPosition", + "USER_TRADES": "/userTrades", + "ORDERS": "/orders", + "GENERATE_READONLY_TOKEN": "/generateReadOnlyToken", + "ACCOUNT": "/account", + "USER_TRANSACTION_HISTORY": "/userTransactionHistory", + "AUTHORIZE": "/authorize", + "ADJUST_LEVERAGE": "/account/adjustLeverage", + "FUND_GAS": "/account/fundGas", + "TRANSFER_HISTORY": "/userTransferHistory", + "FUNDING_HISTORY": "/userFundingHistory", + "CANCEL_ON_DISCONNECT": "/dms-countdown", + }, + "ORDERS": { + "ORDERS": "/orders", + "ORDERS_HASH": "/orders/hash", + }, } -DAPI_BASE_NUM=1000000000000000000 \ No newline at end of file +DAPI_BASE_NUM = 1000000000000000000 diff --git a/src/bluefin_client_sui/contracts.py b/src/bluefin_client_sui/contracts.py index 118a33f..0ddc310 100644 --- a/src/bluefin_client_sui/contracts.py +++ b/src/bluefin_client_sui/contracts.py @@ -2,33 +2,34 @@ _DEFAULT_MARKET = "BTC-PERP" + + class Contracts: def __init__(self): self.contracts_global_info = {} self.contract_info = {} - def set_contract_addresses(self,contracts_info): + def set_contract_addresses(self, contracts_info): self.contract_info = contracts_info - self.contracts_global_info = contracts_info[_DEFAULT_MARKET]['Deployment'] - + self.contracts_global_info = contracts_info[_DEFAULT_MARKET]["Deployment"] + def get_sub_account_id(self): - return self.contracts_global_info['SubAccounts']['id'] - + return self.contracts_global_info["SubAccounts"]["id"] + def get_bank_table_id(self): - return self.contracts_global_info['BankTable']['id'] + return self.contracts_global_info["BankTable"]["id"] def get_package_id(self): - return self.contracts_global_info['package']['id'] + return self.contracts_global_info["package"]["id"] def get_bank_id(self): - return self.contracts_global_info['Bank']['id'] + return self.contracts_global_info["Bank"]["id"] def get_currency_type(self): - return self.contracts_global_info['Currency']['dataType'] + return self.contracts_global_info["Currency"]["dataType"] def get_price_oracle_object_id(self, market: MARKET_SYMBOLS): - return self.contract_info[market]['objects']['PriceOracle']['id'] + return self.contract_info[market]["objects"]["PriceOracle"]["id"] def get_perpetual_id(self, market: MARKET_SYMBOLS): - return self.contract_info[market]['objects']['Perpetual']['id'] - \ No newline at end of file + return self.contract_info[market]["objects"]["Perpetual"]["id"] diff --git a/src/bluefin_client_sui/enumerations.py b/src/bluefin_client_sui/enumerations.py index ef0d818..2c512fb 100644 --- a/src/bluefin_client_sui/enumerations.py +++ b/src/bluefin_client_sui/enumerations.py @@ -1,27 +1,33 @@ from enum import Enum + class ORDER_TYPE(Enum): LIMIT = "LIMIT" MARKET = "MARKET" + class ORDER_SIDE(Enum): BUY = "BUY" SELL = "SELL" + class MARKET_SYMBOLS(Enum): BTC = "BTC-PERP" ETH = "ETH-PERP" + class TIME_IN_FORCE(Enum): FILL_OR_KILL = "FOK" IMMEDIATE_OR_CANCEL = "IOC" GOOD_TILL_TIME = "GTT" + class ONBOARDING_MESSAGES(Enum): ONBOARDING = "Firefly Onboarding" KEY_DERIVATION = "Firefly Access Key" -class ORDER_STATUS(Enum): + +class ORDER_STATUS(Enum): PENDING = "PENDING" OPEN = "OPEN" PARTIAL_FILLED = "PARTIAL_FILLED" @@ -31,6 +37,7 @@ class ORDER_STATUS(Enum): REJECTED = "REJECTED" EXPIRED = "EXPIRED" + class CANCEL_REASON(Enum): UNDERCOLLATERALIZED = "UNDERCOLLATERALIZED" INSUFFICIENT_BALANCE = "INSUFFICIENT_BALANCE" @@ -45,23 +52,25 @@ class CANCEL_REASON(Enum): FAILED = "FAILED" NETWORK_DOWN = "NETWORK_DOWN" + class Interval(Enum): - _1m = "1m" - _3m = "3m" - _5m = "5m" - _15m = "15m" - _30m = "30m" - _1h = "1h" - _2h = "2h" - _4h = "4h" - _6h = "6h" - _8h = "8h" - _12h = "12h" - _1d = "1d" - _3d = "3d" - _1w = "1w" + _1m = "1m" + _3m = "3m" + _5m = "5m" + _15m = "15m" + _30m = "30m" + _1h = "1h" + _2h = "2h" + _4h = "4h" + _6h = "6h" + _8h = "8h" + _12h = "12h" + _1d = "1d" + _3d = "3d" + _1w = "1w" _1M = "1M" + class SOCKET_EVENTS(Enum): GET_LAST_KLINE_WITH_INTERVAL = "{symbol}@kline@{interval}" # rooms that can be joined @@ -84,7 +93,6 @@ class SOCKET_EVENTS(Enum): ACCOUNT_DATA = "AccountDataUpdate" - class MARGIN_TYPE(Enum): ISOLATED = "ISOLATED" CROSS = "CROSS" @@ -94,6 +102,7 @@ class ADJUST_MARGIN(Enum): ADD = "ADD" REMOVE = "REMOVE" + class TRADE_TYPE(Enum): ISOLATED = "IsolatedTrader" - LIQUIDATION = "IsolatedLiquidation" \ No newline at end of file + LIQUIDATION = "IsolatedLiquidation" diff --git a/src/bluefin_client_sui/interfaces.py b/src/bluefin_client_sui/interfaces.py index 50676ad..35036ad 100644 --- a/src/bluefin_client_sui/interfaces.py +++ b/src/bluefin_client_sui/interfaces.py @@ -1,219 +1,243 @@ from typing import TypedDict, List from .enumerations import * + class Order(TypedDict): - market: str - price: int - isBuy: bool - reduceOnly: bool - quantity: int - postOnly: bool - orderbookOnly: bool - leverage: int - expiration: int - salt: int - maker: str - ioc: bool + market: str + price: int + isBuy: bool + reduceOnly: bool + quantity: int + postOnly: bool + orderbookOnly: bool + leverage: int + expiration: int + salt: int + maker: str + ioc: bool class SignedOrder(Order): typedSignature: str -class RequiredOrderFields(TypedDict): - #symbol: MARKET_SYMBOLS # market for which to create order - market: str - price: int # price at which to place order. Will be zero for a market order - quantity: int # quantity/size of order - side: ORDER_SIDE # BUY/SELL - orderType: ORDER_TYPE # MARKET/LIMIT - - -class OrderSignatureRequest(RequiredOrderFields): - leverage: int # (optional) leverage to take, default is 1 - reduceOnly: bool # (optional) is order to be reduce only true/false, default its false - salt: int # (optional) random number for uniqueness of order. Generated randomly if not provided - expiration: int # (optional) time at which order will expire. Will be set to 1 month if not provided - maker: str # (optional) maker of the order, if not provided the account used to initialize the client will be default maker - #isBuy: bool - postOnly: bool - orderBookOnly: bool - ioc: bool + +class RequiredOrderFields(TypedDict): + # symbol: MARKET_SYMBOLS # market for which to create order + market: str + price: int # price at which to place order. Will be zero for a market order + quantity: int # quantity/size of order + side: ORDER_SIDE # BUY/SELL + orderType: ORDER_TYPE # MARKET/LIMIT + + +class OrderSignatureRequest(RequiredOrderFields): + leverage: int # (optional) leverage to take, default is 1 + reduceOnly: bool # (optional) is order to be reduce only true/false, default its false + salt: int # (optional) random number for uniqueness of order. Generated randomly if not provided + expiration: int # (optional) time at which order will expire. Will be set to 1 month if not provided + maker: str # (optional) maker of the order, if not provided the account used to initialize the client will be default maker + # isBuy: bool + postOnly: bool + orderBookOnly: bool + ioc: bool + class OrderSignatureResponse(RequiredOrderFields): - maker: str - orderSignature: str + maker: str + orderSignature: str + class PlaceOrderRequest(OrderSignatureResponse): - timeInForce: TIME_IN_FORCE # FOK/IOC/GTT by default all orders are GTT - postOnly: bool # true/false, default is true - cancelOnRevert: bool # if true, the order will be cancelled in case of on-chain settlement error, default is false - clientId: str # id of the client + timeInForce: TIME_IN_FORCE # FOK/IOC/GTT by default all orders are GTT + postOnly: bool # true/false, default is true + cancelOnRevert: bool # if true, the order will be cancelled in case of on-chain settlement error, default is false + clientId: str # id of the client + class GetOrderbookRequest(TypedDict): - symbol: MARKET_SYMBOLS - limit: int # number of bids/asks to retrieve, should be <= 50 + symbol: MARKET_SYMBOLS + limit: int # number of bids/asks to retrieve, should be <= 50 + class OnboardingMessage(TypedDict): action: str onlySignOn: str + class OrderResponse(TypedDict): - id: int - clientId: str - requestTime: int - cancelReason: CANCEL_REASON - orderStatus: ORDER_STATUS - hash: str - symbol: MARKET_SYMBOLS - orderType: ORDER_TYPE - timeInForce: TIME_IN_FORCE - userAddress: str - side: ORDER_SIDE - price: str - quantity: str - leverage: str - reduceOnly: bool - expiration: int - salt: int - orderSignature: str - filledQty: str - avgFillPrice: str - createdAt: int - updatedAt: int - makerFee: str - takerFee: str - openQty: str - cancelOnRevert: bool + id: int + clientId: str + requestTime: int + cancelReason: CANCEL_REASON + orderStatus: ORDER_STATUS + hash: str + symbol: MARKET_SYMBOLS + orderType: ORDER_TYPE + timeInForce: TIME_IN_FORCE + userAddress: str + side: ORDER_SIDE + price: str + quantity: str + leverage: str + reduceOnly: bool + expiration: int + salt: int + orderSignature: str + filledQty: str + avgFillPrice: str + createdAt: int + updatedAt: int + makerFee: str + takerFee: str + openQty: str + cancelOnRevert: bool class GetOrderResponse(OrderResponse): - fee: str - postOnly: bool - triggerPrice: str + fee: str + postOnly: bool + triggerPrice: str class GetCandleStickRequest(TypedDict): - symbol: MARKET_SYMBOLS - interval: Interval - startTime: float - endTime: float - limit: int + symbol: MARKET_SYMBOLS + interval: Interval + startTime: float + endTime: float + limit: int + class GetMarketRecentTradesRequest(TypedDict): - symbol: MARKET_SYMBOLS - pageSize: int - pageNumber: int - traders: TRADE_TYPE + symbol: MARKET_SYMBOLS + pageSize: int + pageNumber: int + traders: TRADE_TYPE + class OrderCancelSignatureRequest(TypedDict): - symbol: MARKET_SYMBOLS - hashes: list - parentAddress: str # (optional) should only be provided by a sub account + symbol: MARKET_SYMBOLS + hashes: list + parentAddress: str # (optional) should only be provided by a sub account + class OrderCancellationRequest(OrderCancelSignatureRequest): - signature: str + signature: str + class CancelOrder(TypedDict): - hash: str - reason: str + hash: str + reason: str class CancelOrderResponse(TypedDict): - message: str - data: dict + message: str + data: dict class GetTransactionHistoryRequest(TypedDict): - symbol: MARKET_SYMBOLS # will fetch orders of provided market - pageSize: int # will get only provided number of orders must be <= 50 - pageNumber: int # will fetch particular page records. A single page contains 50 records. + symbol: MARKET_SYMBOLS # will fetch orders of provided market + pageSize: int # will get only provided number of orders must be <= 50 + pageNumber: int # will fetch particular page records. A single page contains 50 records. + class GetPositionRequest(GetTransactionHistoryRequest): - parentAddress : str # (optional) should be provided by sub accounts + parentAddress: str # (optional) should be provided by sub accounts + class GetUserTradesRequest(TypedDict): - symbol: MARKET_SYMBOLS - maker: bool - fromId: int - startTime: int - endTime: int - pageSize: int - pageNumber: int - type: ORDER_TYPE - parentAddress: str # (optional) should be provided by sub account + symbol: MARKET_SYMBOLS + maker: bool + fromId: int + startTime: int + endTime: int + pageSize: int + pageNumber: int + type: ORDER_TYPE + parentAddress: str # (optional) should be provided by sub account + class GetOrderRequest(GetTransactionHistoryRequest): - statuses:List[ORDER_STATUS] # (optional) status of orders to be fetched - orderId: int #(optional) the id of order to be fetched - orderType: List[ORDER_TYPE] # (optional) type of order Limit/Market - orderHashes: List[str] # (optional) hashes of order to be fetched - parentAddress : str # (optional) should be provided by sub accounts + statuses: List[ORDER_STATUS] # (optional) status of orders to be fetched + orderId: int # (optional) the id of order to be fetched + orderType: List[ORDER_TYPE] # (optional) type of order Limit/Market + orderHashes: List[str] # (optional) hashes of order to be fetched + parentAddress: str # (optional) should be provided by sub accounts + class GetFundingHistoryRequest(TypedDict): - symbol: MARKET_SYMBOLS # will fetch orders of provided market - pageSize: int # will get only provided number of orders must be <= 50 - cursor: int # will fetch particular page records. A single page contains 50 records. - parentAddress: str # (optional) should be provided by a sub account + symbol: MARKET_SYMBOLS # will fetch orders of provided market + pageSize: int # will get only provided number of orders must be <= 50 + cursor: int # will fetch particular page records. A single page contains 50 records. + parentAddress: str # (optional) should be provided by a sub account class FundingHistoryResponse(TypedDict): - id: int # unique id - symbol: MARKET_SYMBOLS # market for which to create order - userAddress: str # user public address - quantity: int # size of position - time: int # created time - appliedFundingRate: str # funding rate percent applied - isFundingRatePositive: bool # was funding rate +ve or -ve - payment: str # amount - isPaymentPositive: bool # whether payment was deducted or added - oraclePrice: str # price from oracle - side: ORDER_SIDE # BUY/SELL - blockNumber: int # transaction block number - isPositionPositive: bool # is position LONG or SHORT + id: int # unique id + symbol: MARKET_SYMBOLS # market for which to create order + userAddress: str # user public address + quantity: int # size of position + time: int # created time + appliedFundingRate: str # funding rate percent applied + isFundingRatePositive: bool # was funding rate +ve or -ve + payment: str # amount + isPaymentPositive: bool # whether payment was deducted or added + oraclePrice: str # price from oracle + side: ORDER_SIDE # BUY/SELL + blockNumber: int # transaction block number + isPositionPositive: bool # is position LONG or SHORT + class GetFundingHistoryResponse(TypedDict): - isMoreDataAvailable: bool # boolean indicating if there is more data available - nextCursor: int # next page number - data: List[FundingHistoryResponse] + isMoreDataAvailable: bool # boolean indicating if there is more data available + nextCursor: int # next page number + data: List[FundingHistoryResponse] + class GetTransferHistoryRequest(TypedDict): - pageSize: int # will get only provided number of orders must be <= 50 - cursor: int # will fetch particular page records. A single page contains 50 records. - action: str # (optional) Deposit / Withdraw + pageSize: int # will get only provided number of orders must be <= 50 + cursor: int # will fetch particular page records. A single page contains 50 records. + action: str # (optional) Deposit / Withdraw + class UserTransferHistoryResponse(TypedDict): - id: int # unique id - status: str # status of transaction - action: str # Deposit / Withdraw - amount: str # amount withdrawn/deposited - userAddress: str # user public address - blockNumber: int # transaction block number - latestTxHash: str # transaction hash - time: int # created time - createdAt: int - updatedAt: int + id: int # unique id + status: str # status of transaction + action: str # Deposit / Withdraw + amount: str # amount withdrawn/deposited + userAddress: str # user public address + blockNumber: int # transaction block number + latestTxHash: str # transaction hash + time: int # created time + createdAt: int + updatedAt: int + class GetUserTransferHistoryResponse(TypedDict): - isMoreDataAvailable: bool # boolean indicating if there is more data available - nextCursor: int # next page number - data: List[UserTransferHistoryResponse] + isMoreDataAvailable: bool # boolean indicating if there is more data available + nextCursor: int # next page number + data: List[UserTransferHistoryResponse] + class CountDown(TypedDict): - symbol: str - countDown: int + symbol: str + countDown: int + class GetCancelOnDisconnectTimerRequest(TypedDict): - symbol: MARKET_SYMBOLS # will fetch Cancel On Disconnect Timer of provided market - parentAddress: str # (optional) should be provided by a sub account + symbol: MARKET_SYMBOLS # will fetch Cancel On Disconnect Timer of provided market + parentAddress: str # (optional) should be provided by a sub account + class PostTimerAttributes(TypedDict): - countDowns: List[CountDown] - parentAddress: str + countDowns: List[CountDown] + parentAddress: str + class FailedCountDownResetResponse(TypedDict): - symbol: str - reason: str + symbol: str + reason: str + class PostTimerResponse(TypedDict): - acceptedToReset: List[str] - failedReset: List[FailedCountDownResetResponse] + acceptedToReset: List[str] + failedReset: List[FailedCountDownResetResponse] diff --git a/src/bluefin_client_sui/onboarding_signer.py b/src/bluefin_client_sui/onboarding_signer.py index 308f5c5..4b280bc 100644 --- a/src/bluefin_client_sui/onboarding_signer.py +++ b/src/bluefin_client_sui/onboarding_signer.py @@ -1,29 +1,29 @@ from .interfaces import * from .signer import Signer -import hashlib +import hashlib import json + class OnboardingSigner(Signer): def __init__(self): super().__init__() def create_signature(self, msg, private_key, encoding="utf-8"): """ - Signs the message. - Inputs: - - msg: the message to be signed - - private_key: the signer's private key - Returns: - - str: signed msg hash + Signs the message. + Inputs: + - msg: the message to be signed + - private_key: the signer's private key + Returns: + - str: signed msg hash """ - msgDict={} - msgDict['onboardingUrl']=msg - msg=json.dumps(msgDict,separators=(',', ':')) - msg_bytearray=bytearray(msg.encode("utf-8")) - intent=bytearray() - intent.extend([3,0,0, len(msg_bytearray)]) - intent=intent+msg_bytearray + msgDict = {} + msgDict["onboardingUrl"] = msg + msg = json.dumps(msgDict, separators=(",", ":")) + msg_bytearray = bytearray(msg.encode("utf-8")) + intent = bytearray() + intent.extend([3, 0, 0, len(msg_bytearray)]) + intent = intent + msg_bytearray - hash=hashlib.blake2b(intent,digest_size=32) + hash = hashlib.blake2b(intent, digest_size=32) return self.sign_hash(hash.digest(), private_key) - diff --git a/src/bluefin_client_sui/order_signer.py b/src/bluefin_client_sui/order_signer.py index 59d821a..0b2c5c3 100644 --- a/src/bluefin_client_sui/order_signer.py +++ b/src/bluefin_client_sui/order_signer.py @@ -3,78 +3,87 @@ from .interfaces import Order import hashlib + class OrderSigner(Signer): def __init__(self, version="1.0"): super().__init__() self.version = version def get_order_flags(self, order): - - ''' 0th bit = ioc - 1st bit = postOnly - 2nd bit = reduceOnly - 3rd bit = isBuy - 4th bit = orderbookOnly - e.g. 00000000 // all flags false - e.g. 00000001 // ioc order, sell side, can be executed by taker - e.e. 00010001 // same as above but can only be executed by settlement operator - ''' - flag = 0 - if order['ioc']: - flag+=1 - if order['postOnly']: - flag+=2 - if order['reduceOnly']: - flag+=4 - if order['isBuy']: - flag+=8 - if order['orderbookOnly']: - flag+=16 + """0th bit = ioc + 1st bit = postOnly + 2nd bit = reduceOnly + 3rd bit = isBuy + 4th bit = orderbookOnly + e.g. 00000000 // all flags false + e.g. 00000001 // ioc order, sell side, can be executed by taker + e.e. 00010001 // same as above but can only be executed by settlement operator + """ + flag = 0 + if order["ioc"]: + flag += 1 + if order["postOnly"]: + flag += 2 + if order["reduceOnly"]: + flag += 4 + if order["isBuy"]: + flag += 8 + if order["orderbookOnly"]: + flag += 16 return flag - - def get_order_hash(self, order:Order, withBufferHex=True): + def get_order_hash(self, order: Order, withBufferHex=True): """ - Returns order hash. - Inputs: - - order: the order to be signed - Returns: - - str: order hash + Returns order hash. + Inputs: + - order: the order to be signed + Returns: + - str: order hash """ flags = self.get_order_flags(order) - flags = hexToByteArray(numberToHex(flags,2)) - - buffer=bytearray() - orderPriceHex=hexToByteArray(numberToHex(int(order["price"]))) - orderQuantityHex=hexToByteArray(numberToHex(int(order['quantity']))) - orderLeverageHex=hexToByteArray (numberToHex(int(order['leverage']))) - orderSalt=hexToByteArray(numberToHex(int(order['salt']))) - orderExpiration=hexToByteArray(numberToHex(int(order['expiration']),16)) - orderMaker=hexToByteArray(numberToHex(int(order['maker'],16),64)) - orderMarket=hexToByteArray(numberToHex(int(order['market'],16),64)) - bluefin=bytearray("Bluefin", encoding="utf-8") + flags = hexToByteArray(numberToHex(flags, 2)) - buffer=orderPriceHex+orderQuantityHex+orderLeverageHex+orderSalt+orderExpiration+orderMaker+orderMarket+flags+bluefin + buffer = bytearray() + orderPriceHex = hexToByteArray(numberToHex(int(order["price"]))) + orderQuantityHex = hexToByteArray(numberToHex(int(order["quantity"]))) + orderLeverageHex = hexToByteArray(numberToHex(int(order["leverage"]))) + orderSalt = hexToByteArray(numberToHex(int(order["salt"]))) + orderExpiration = hexToByteArray(numberToHex(int(order["expiration"]), 16)) + orderMaker = hexToByteArray(numberToHex(int(order["maker"], 16), 64)) + orderMarket = hexToByteArray(numberToHex(int(order["market"], 16), 64)) + bluefin = bytearray("Bluefin", encoding="utf-8") - #for cancel order signature verification we use buffer directly + buffer = ( + orderPriceHex + + orderQuantityHex + + orderLeverageHex + + orderSalt + + orderExpiration + + orderMaker + + orderMarket + + flags + + bluefin + ) + + # for cancel order signature verification we use buffer directly # for placing order we use buffer.hex().encode("utf-8") if withBufferHex: - order_hash=hashlib.sha256(buffer.hex().encode("utf-8")).digest() + order_hash = hashlib.sha256(buffer.hex().encode("utf-8")).digest() else: - order_hash=hashlib.sha256(buffer).digest() - return order_hash + order_hash = hashlib.sha256(buffer).digest() + return order_hash - def sign_order(self, order:Order, private_key): + def sign_order(self, order: Order, private_key): """ - Used to create an order signature. The method will use the provided key - in params to sign the order. + Used to create an order signature. The method will use the provided key + in params to sign the order. + + Args: + order (Order): an order containing order fields (look at Order interface) + private_key (str): private key of the account to be used for signing - Args: - order (Order): an order containing order fields (look at Order interface) - private_key (str): private key of the account to be used for signing - - Returns: - str: generated signature + Returns: + str: generated signature """ order_hash = self.get_order_hash(order) return self.sign_hash(order_hash, private_key, "") diff --git a/src/bluefin_client_sui/rpc.py b/src/bluefin_client_sui/rpc.py index 0a51af7..7ff4752 100644 --- a/src/bluefin_client_sui/rpc.py +++ b/src/bluefin_client_sui/rpc.py @@ -1,64 +1,77 @@ import requests import json -def rpc_unsafe_moveCall(url,params, function_name: str, function_library: str, userAddress ,packageId, gasBudget=100000000 ): - base_dict={} - base_dict["jsonrpc"]="2.0" - base_dict["id"]=1689764924887 - base_dict["method"]="unsafe_moveCall" - base_dict["params"]=[] - base_dict["params"].extend([userAddress, packageId, function_library,function_name]) + +def rpc_unsafe_moveCall( + url, + params, + function_name: str, + function_library: str, + userAddress, + packageId, + gasBudget=100000000, +): + base_dict = {} + base_dict["jsonrpc"] = "2.0" + base_dict["id"] = 1689764924887 + base_dict["method"] = "unsafe_moveCall" + base_dict["params"] = [] + base_dict["params"].extend( + [userAddress, packageId, function_library, function_name] + ) base_dict["params"].append([]) base_dict["params"].append(params) base_dict["params"].append(None) base_dict["params"].append(str(gasBudget)) - payload=json.dumps(base_dict) + payload = json.dumps(base_dict) - headers = {'Content-Type': 'application/json' } + headers = {"Content-Type": "application/json"} response = requests.request("POST", url, headers=headers, data=payload) - result=json.loads(response.text) - return result['result']['txBytes'] + result = json.loads(response.text) + return result["result"]["txBytes"] + def rpc_sui_executeTransactionBlock(url, txBytes, signature): - base_dict={} - base_dict["jsonrpc"]="2.0" - base_dict["id"]=5 - base_dict["method"]="sui_executeTransactionBlock" - base_dict["params"]=[] + base_dict = {} + base_dict["jsonrpc"] = "2.0" + base_dict["id"] = 5 + base_dict["method"] = "sui_executeTransactionBlock" + base_dict["params"] = [] base_dict["params"].append(txBytes) base_dict["params"].append([signature]) - outputTypeDict={ - "showInput": True, - "showEffects": True, - "showEvents": True, - "showObjectChanges": True + outputTypeDict = { + "showInput": True, + "showEffects": True, + "showEvents": True, + "showObjectChanges": True, } - base_dict["params"].append( outputTypeDict) + base_dict["params"].append(outputTypeDict) base_dict["params"].append("WaitForLocalExecution") - payload=json.dumps(base_dict) + payload = json.dumps(base_dict) - headers = {'Content-Type': 'application/json' } + headers = {"Content-Type": "application/json"} response = requests.request("POST", url, headers=headers, data=payload) - result=json.loads(response.text) + result = json.loads(response.text) return result def rpc_call_sui_function(url, params, method="suix_getCoins"): - base_dict={} - base_dict["jsonrpc"]="2.0" - base_dict["id"]= 1 - base_dict["method"]=method - base_dict["params"]=params - payload=json.dumps(base_dict) - - headers = {'Content-Type': 'application/json' } + base_dict = {} + base_dict["jsonrpc"] = "2.0" + base_dict["id"] = 1 + base_dict["method"] = method + base_dict["params"] = params + payload = json.dumps(base_dict) + + headers = {"Content-Type": "application/json"} response = requests.request("POST", url, headers=headers, data=payload) - result=json.loads(response.text) + result = json.loads(response.text) return result["result"] + """ payload = json.dumps({ @@ -91,12 +104,6 @@ def rpc_call_sui_function(url, params, method="suix_getCoins"): """ - - - - - - """ url = "https://fullnode.testnet.sui.io:443" @@ -126,4 +133,4 @@ def rpc_call_sui_function(url, params, method="suix_getCoins"): response = requests.request("POST", url, headers=headers, data=payload) print(response.text) -""" \ No newline at end of file +""" diff --git a/src/bluefin_client_sui/signer.py b/src/bluefin_client_sui/signer.py index dc599b3..a378749 100644 --- a/src/bluefin_client_sui/signer.py +++ b/src/bluefin_client_sui/signer.py @@ -4,55 +4,53 @@ import base64 from .utilities import * from .account import * + + class Signer: def __init__(self): pass - def sign_tx(self, tx_bytes_str: str, sui_wallet: SuiWallet) -> str: """ - expects the msg in str - expects the suiwallet object - Signs the msg and returns the signature. - Returns the value in b64 encoded format + expects the msg in str + expects the suiwallet object + Signs the msg and returns the signature. + Returns the value in b64 encoded format """ - tx_bytes=base64.b64decode(tx_bytes_str) + tx_bytes = base64.b64decode(tx_bytes_str) - intent=bytearray() - intent.extend([0,0,0]) - intent=intent+tx_bytes - hash=hashlib.blake2b(intent,digest_size=32).digest() + intent = bytearray() + intent.extend([0, 0, 0]) + intent = intent + tx_bytes + hash = hashlib.blake2b(intent, digest_size=32).digest() - - result= nacl.signing.SigningKey(sui_wallet.privateKeyBytes).sign(hash)[:64] - temp=bytearray() + result = nacl.signing.SigningKey(sui_wallet.privateKeyBytes).sign(hash)[:64] + temp = bytearray() temp.append(0) temp.extend(result) temp.extend(sui_wallet.publicKeyBytes[1:]) - res=base64.b64encode(temp) + res = base64.b64encode(temp) return res.decode() - - def sign_hash(self, hash, private_key, append=''): + def sign_hash(self, hash, private_key, append=""): """ - Signs the hash and returns the signature. + Signs the hash and returns the signature. """ - result= nacl.signing.SigningKey(private_key).sign(hash)[:64] - return result.hex()+'1' + append - + result = nacl.signing.SigningKey(private_key).sign(hash)[:64] + return result.hex() + "1" + append - def encode_message(self,msg: dict): - msg=json.dumps(msg,separators=(',', ':')) - msg_bytearray=bytearray(msg.encode("utf-8")) - intent=bytearray() - encodeLengthBCS=self.decimal_to_bcs(len(msg_bytearray)) - intent.extend([3,0,0]) + def encode_message(self, msg: dict): + msg = json.dumps(msg, separators=(",", ":")) + msg_bytearray = bytearray(msg.encode("utf-8")) + intent = bytearray() + encodeLengthBCS = self.decimal_to_bcs(len(msg_bytearray)) + intent.extend([3, 0, 0]) intent.extend(encodeLengthBCS) - intent=intent+msg_bytearray - hash=hashlib.blake2b(intent,digest_size=32) + intent = intent + msg_bytearray + hash = hashlib.blake2b(intent, digest_size=32) return hash.digest() - - def decimal_to_bcs(self,num): + + def decimal_to_bcs(self, num): # Initialize an empty list to store the BCS bytes bcs_bytes = [] while num > 0: @@ -70,4 +68,3 @@ def decimal_to_bcs(self,num): num >>= 7 return bcs_bytes - \ No newline at end of file diff --git a/src/bluefin_client_sui/socket_manager.py b/src/bluefin_client_sui/socket_manager.py index 9a13fe7..b834bb7 100644 --- a/src/bluefin_client_sui/socket_manager.py +++ b/src/bluefin_client_sui/socket_manager.py @@ -100,4 +100,4 @@ def _callback(self, callback, *args): except Exception as e: self.logger.error("Error from callback {}: {}".format(callback, e)) if self.on_error: - self.on_error(self, e) \ No newline at end of file + self.on_error(self, e) diff --git a/src/bluefin_client_sui/sockets_lib.py b/src/bluefin_client_sui/sockets_lib.py index f883973..3a4f2f0 100644 --- a/src/bluefin_client_sui/sockets_lib.py +++ b/src/bluefin_client_sui/sockets_lib.py @@ -2,6 +2,7 @@ import time from .enumerations import MARKET_SYMBOLS, SOCKET_EVENTS import asyncio + sio = socketio.Client() @@ -17,34 +18,33 @@ def __init__(self, url, timeout=10, token=None) -> None: def _establish_connection(self): """ - Connects to the desired url + Connects to the desired url """ try: - sio.connect(self.url, wait_timeout=self.timeout, - transports=["websocket"]) + sio.connect(self.url, wait_timeout=self.timeout, transports=["websocket"]) return True except: return False def set_token(self, token): """ - Sets default user token - Inputs: - - token (user auth token): firefly onboarding token. + Sets default user token + Inputs: + - token (user auth token): Bluefin onboarding token. """ self.token = token def set_api_token(self, token): """ - Sets default user token - Inputs: - - token (user auth token): firefly onboarding token. + Sets default user token + Inputs: + - token (user auth token): Bluefin onboarding token. """ self.api_token = token async def open(self): """ - opens socket instance connection + opens socket instance connection """ self.connection_established = self._establish_connection() if not self.connection_established: @@ -54,7 +54,7 @@ async def open(self): async def close(self): """ - closes the socket instance connection + closes the socket instance connection """ sio.disconnect() return @@ -62,7 +62,7 @@ async def close(self): @sio.on("*") def listener(event, data): """ - Listens to all events emitted by the server + Listens to all events emitted by the server """ try: if event in Sockets.callbacks.keys(): @@ -74,49 +74,51 @@ def listener(event, data): except: pass return - + @sio.event def connect(): print("Connected To Socket Server") # add 10 seconds sleep to allow connection to be established before callbacks for connections are executed - if 'connect' in Sockets.callbacks: + if "connect" in Sockets.callbacks: # Execute the callback using asyncio.run() if available time.sleep(10) - asyncio.run(Sockets.callbacks['connect']()) - + asyncio.run(Sockets.callbacks["connect"]()) @sio.event def disconnect(): - print('Disconnected From Socket Server') - if 'disconnect' in Sockets.callbacks: + print("Disconnected From Socket Server") + if "disconnect" in Sockets.callbacks: # Execute the callback using asyncio.run() if available - asyncio.run(Sockets.callbacks['disconnect']()) - + asyncio.run(Sockets.callbacks["disconnect"]()) async def listen(self, event, callback): """ - Assigns callbacks to desired events + Assigns callbacks to desired events """ Sockets.callbacks[event] = callback return async def subscribe_global_updates_by_symbol(self, symbol: MARKET_SYMBOLS): """ - Allows user to subscribe to global updates for the desired symbol. - Inputs: - - symbol: market symbol of market user wants global updates for. (e.g. DOT-PERP) + Allows user to subscribe to global updates for the desired symbol. + Inputs: + - symbol: market symbol of market user wants global updates for. (e.g. DOT-PERP) """ try: if not self.connection_established: raise Exception( - "Socket connection is established, invoke socket.open()") - - resp = sio.call('SUBSCRIBE', [ - { - "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, - "p": symbol.value, - }, - ]) + "Socket connection is established, invoke socket.open()" + ) + + resp = sio.call( + "SUBSCRIBE", + [ + { + "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, + "p": symbol.value, + }, + ], + ) return resp["success"] except Exception as e: print("Error: ", e) @@ -124,71 +126,84 @@ async def subscribe_global_updates_by_symbol(self, symbol: MARKET_SYMBOLS): async def unsubscribe_global_updates_by_symbol(self, symbol: MARKET_SYMBOLS): """ - Allows user to unsubscribe to global updates for the desired symbol. - Inputs: - - symbol: market symbol of market user wants to remove global updates for. (e.g. DOT-PERP) + Allows user to unsubscribe to global updates for the desired symbol. + Inputs: + - symbol: market symbol of market user wants to remove global updates for. (e.g. DOT-PERP) """ try: if not self.connection_established: return False - resp = sio.call('UNSUBSCRIBE', [ - { - "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, - "p": symbol.value, - }, - ]) + resp = sio.call( + "UNSUBSCRIBE", + [ + { + "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, + "p": symbol.value, + }, + ], + ) return resp["success"] except Exception as e: print(e) return False - async def subscribe_user_update_by_token(self, parent_account: str = None, user_token: str = None) -> bool: + async def subscribe_user_update_by_token( + self, parent_account: str = None, user_token: str = None + ) -> bool: """ - Allows user to subscribe to their account updates. - Inputs: - - parent_account(str): address of parent account. Only whitelisted - sub-account can listen to its parent account position updates - - token(str): auth token generated when onboarding on firefly + Allows user to subscribe to their account updates. + Inputs: + - parent_account(str): address of parent account. Only whitelisted + sub-account can listen to its parent account position updates + - token(str): auth token generated when onboarding on Bluefin """ try: if not self.connection_established: return False - resp = sio.call("SUBSCRIBE", [ - { - "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, - 'pa': parent_account, - "t": self.token if user_token == None else user_token, - "rt": self.api_token - }, - ]) + resp = sio.call( + "SUBSCRIBE", + [ + { + "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, + "pa": parent_account, + "t": self.token if user_token == None else user_token, + "rt": self.api_token, + }, + ], + ) return resp["success"] except Exception as e: print(e) return False - async def unsubscribe_user_update_by_token(self, parent_account: str = None, user_token: str = None): + async def unsubscribe_user_update_by_token( + self, parent_account: str = None, user_token: str = None + ): """ - Allows user to unsubscribe to their account updates. - Inputs: - - parent_account(str): address of parent account. Only for sub-accounts - - token: auth token generated when onboarding on firefly + Allows user to unsubscribe to their account updates. + Inputs: + - parent_account(str): address of parent account. Only for sub-accounts + - token: auth token generated when onboarding on Bluefin """ try: if not self.connection_established: return False - resp = sio.call("UNSUBSCRIBE", [ - { - "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, - 'pa': parent_account, - "t": self.token if user_token == None else user_token, - "rt": self.api_token - }, - ]) + resp = sio.call( + "UNSUBSCRIBE", + [ + { + "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, + "pa": parent_account, + "t": self.token if user_token == None else user_token, + "rt": self.api_token, + }, + ], + ) return resp["success"] except: return False diff --git a/src/bluefin_client_sui/utilities.py b/src/bluefin_client_sui/utilities.py index b9dd2d2..2f89d7f 100644 --- a/src/bluefin_client_sui/utilities.py +++ b/src/bluefin_client_sui/utilities.py @@ -1,73 +1,79 @@ from datetime import datetime from random import randint -#from web3 import Web3 + +# from web3 import Web3 import time import bip_utils import hashlib from typing import Union from .constants import SUI_BASE_NUM, DAPI_BASE_NUM -def toDapiBase(number: Union[int, float])->int: - return int(number*DAPI_BASE_NUM) -def fromDapiBase(number: Union[int, float], dtype=int)-> int: - return dtype(number/DAPI_BASE_NUM) +def toDapiBase(number: Union[int, float]) -> int: + return int(number * DAPI_BASE_NUM) + + +def fromDapiBase(number: Union[int, float], dtype=int) -> int: + return dtype(number / DAPI_BASE_NUM) def numberToHex(num, pad=32): - #converting number to Hexadecimal format - hexNum=hex(num) + # converting number to Hexadecimal format + hexNum = hex(num) - #padding it with zero to make the size 32 bytes - padHex=hexNum[2:].zfill(pad) + # padding it with zero to make the size 32 bytes + padHex = hexNum[2:].zfill(pad) return padHex + def hexToByteArray(hexStr): return bytearray.fromhex(hexStr) -def mnemonicToPrivateKey(seedPhrase: str)-> str: +def mnemonicToPrivateKey(seedPhrase: str) -> str: bip39_seed = bip_utils.Bip39SeedGenerator(seedPhrase).Generate() bip32_ctx = bip_utils.Bip32Slip10Ed25519.FromSeed(bip39_seed) - derivation_path="m/44'/784'/0'/0'/0'" + derivation_path = "m/44'/784'/0'/0'/0'" bip32_der_ctx = bip32_ctx.DerivePath(derivation_path) private_key: str = bip32_der_ctx.PrivateKey().Raw() return private_key -def privateKeyToPublicKey(privateKey: str)-> str: - privateKeyBytes=bytes(privateKey) + +def privateKeyToPublicKey(privateKey: str) -> str: + privateKeyBytes = bytes(privateKey) bip32_ctx = bip_utils.Bip32Slip10Ed25519.FromPrivateKey(privateKeyBytes) - public_key: str = bip32_ctx.PublicKey().RawCompressed() + public_key: str = bip32_ctx.PublicKey().RawCompressed() return public_key -def getAddressFromPublicKey(publicKey: str)-> str: - address: str = "0x" + hashlib.blake2b(publicKey.ToBytes(), digest_size=32).digest().hex()[:] - return address - - - - - +def getAddressFromPublicKey(publicKey: str) -> str: + address: str = ( + "0x" + hashlib.blake2b(publicKey.ToBytes(), digest_size=32).digest().hex()[:] + ) + return address def strip_hex_prefix(input): - if input[0:2] == '0x': + if input[0:2] == "0x": return input[2:] else: return input + def address_to_bytes32(addr): - return '0x000000000000000000000000' + strip_hex_prefix(addr) + return "0x000000000000000000000000" + strip_hex_prefix(addr) + + +def bn_to_bytes8(value: int): + return str("0x" + "0" * 16 + hex(value)[2:]).encode("utf-8") -def bn_to_bytes8(value:int): - return str("0x"+"0"*16+hex(value)[2:]).encode('utf-8') def default_value(dict, key, default_value): if key in dict: return dict[key] else: - return default_value + return default_value + def default_enum_value(dict, key, default_value): if key in dict: @@ -77,18 +83,21 @@ def default_enum_value(dict, key, default_value): def current_unix_timestamp(): - return int(datetime.now().timestamp()) + return int(datetime.now().timestamp()) + def random_number(max_range): return current_unix_timestamp() + randint(0, max_range) + randint(0, max_range) -def extract_query(value:dict): - query="" - for i,j in value.items(): - query+="&{}={}".format(i,j) + +def extract_query(value: dict): + query = "" + for i, j in value.items(): + query += "&{}={}".format(i, j) return query[1:] -def extract_enums(params:dict,enums:list): + +def extract_enums(params: dict, enums: list): for i in enums: if i in params.keys(): if type(params[i]) == list: @@ -97,6 +106,7 @@ def extract_enums(params:dict,enums:list): params[i] = params[i].value return params + def config_logging(logging, logging_level, log_file: str = None): """Configures logging to provide a more detailed log format, which includes date time in UTC Example: 2021-11-02 19:42:04.849 UTC : @@ -114,4 +124,4 @@ def config_logging(logging, logging_level, log_file: str = None): filename=log_file, format="%(asctime)s.%(msecs)03d UTC %(levelname)s %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", - ) \ No newline at end of file + ) diff --git a/src/bluefin_client_sui/websocket_client.py b/src/bluefin_client_sui/websocket_client.py index 34c87af..3544232 100644 --- a/src/bluefin_client_sui/websocket_client.py +++ b/src/bluefin_client_sui/websocket_client.py @@ -47,23 +47,23 @@ def initialize_socket( def set_token(self, token): """ - Sets default user token - Inputs: - - token (user auth token): firefly onboarding token. + Sets default user token + Inputs: + - token (user auth token): Bluefin onboarding token. """ self.token = token def set_api_token(self, token): """ - Sets default user token - Inputs: - - token (user auth token): firefly onboarding token. + Sets default user token + Inputs: + - token (user auth token): Bluefin onboarding token. """ self.api_token = token def listen(self, event, callback): """ - Assigns callbacks to desired events + Assigns callbacks to desired events """ self.callbacks[event] = callback return @@ -73,83 +73,122 @@ def send(self, message: dict): def subscribe_global_updates_by_symbol(self, symbol: MARKET_SYMBOLS): """ - Allows user to subscribe to global updates for the desired symbol. - Inputs: - - symbol: market symbol of market user wants global updates for. (e.g. DOT-PERP) + Allows user to subscribe to global updates for the desired symbol. + Inputs: + - symbol: market symbol of market user wants global updates for. (e.g. DOT-PERP) """ try: if not self.socket_manager.ws.connected: raise Exception( - "Socket connection is established, invoke socket.open()") - - self.socket_manager.send_message(json.dumps(['SUBSCRIBE', [ - { - "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, - "p": symbol.value, - }, - ]])) + "Socket connection is established, invoke socket.open()" + ) + + self.socket_manager.send_message( + json.dumps( + [ + "SUBSCRIBE", + [ + { + "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, + "p": symbol.value, + }, + ], + ] + ) + ) return True except Exception: return False def unsubscribe_global_updates_by_symbol(self, symbol: MARKET_SYMBOLS): """ - Allows user to unsubscribe to global updates for the desired symbol. - Inputs: - - symbol: market symbol of market user wants to remove global updates for. (e.g. DOT-PERP) + Allows user to unsubscribe to global updates for the desired symbol. + Inputs: + - symbol: market symbol of market user wants to remove global updates for. (e.g. DOT-PERP) """ try: if not self.socket_manager.ws.connected: return False - self.socket_manager.send_message(json.dumps((['UNSUBSCRIBE', [ - { - "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, - "p": symbol.value, - }, - ]]))) + self.socket_manager.send_message( + json.dumps( + ( + [ + "UNSUBSCRIBE", + [ + { + "e": SOCKET_EVENTS.GLOBAL_UPDATES_ROOM.value, + "p": symbol.value, + }, + ], + ] + ) + ) + ) return True except: return False def subscribe_user_update_by_token(self, user_token: str = None): """ - Allows user to subscribe to their account updates. - Inputs: - - token(str): auth token generated when onboarding on firefly + Allows user to subscribe to their account updates. + Inputs: + - token(str): auth token generated when onboarding on Bluefin """ try: if not self.socket_manager.ws.connected: return False - self.socket_manager.send_message(json.dumps((["SUBSCRIBE", [ - { - "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, - "t": self.token if user_token == None else user_token, - "rt": self.api_token - }, - ]]))) + self.socket_manager.send_message( + json.dumps( + ( + [ + "SUBSCRIBE", + [ + { + "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, + "t": self.token + if user_token == None + else user_token, + "rt": self.api_token, + }, + ], + ] + ) + ) + ) return True except: return False def unsubscribe_user_update_by_token(self, user_token: str = None): """ - Allows user to unsubscribe to their account updates. - Inputs: - - token: auth token generated when onboarding on firefly + Allows user to unsubscribe to their account updates. + Inputs: + - token: auth token generated when onboarding on Bluefin """ try: if not self.socket_manager.ws.connected: return False - self.socket_manager.send_message(json.dumps((["UNSUBSCRIBE", [ - { - "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, - "t": self.token if user_token == None else user_token, - "rt": self.api_token - }, - ]]))) + self.socket_manager.send_message( + json.dumps( + ( + [ + "UNSUBSCRIBE", + [ + { + "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, + "t": self.token + if user_token == None + else user_token, + "rt": self.api_token, + }, + ], + ] + ) + ) + ) return True except: return False @@ -164,7 +203,7 @@ def stop(self, id=None): def listener(self, _, message): """ - Listens to all events emitted by the server + Listens to all events emitted by the server """ data = json.loads(message) event_name = data["eventName"] @@ -173,8 +212,7 @@ def listener(self, _, message): callback = self.callbacks[event_name] callback(data["data"]) elif "default" in self.callbacks.keys(): - self.callbacks["default"]( - {"event": event_name, "data": data["data"]}) + self.callbacks["default"]({"event": event_name, "data": data["data"]}) else: pass except: diff --git a/src/check.py b/src/check.py index cc76e9c..0c62fff 100644 --- a/src/check.py +++ b/src/check.py @@ -1,31 +1,33 @@ -from bluefin_exchange_client_sui import FireflyClient, Networks +from bluefin_exchange_client_sui import BluefinClient, Networks from pprint import pprint import asyncio -TEST_ACCT_KEY="!#12" -TEST_NETWORK="SUI_STAGING" + +TEST_ACCT_KEY = "!#12" +TEST_NETWORK = "SUI_STAGING" async def main(): - # initialize client - client = FireflyClient( - True, # agree to terms and conditions - Networks[TEST_NETWORK], # network to connect with - TEST_ACCT_KEY, # private key of wallet - ) + # initialize client + client = BluefinClient( + True, # agree to terms and conditions + Networks[TEST_NETWORK], # network to connect with + TEST_ACCT_KEY, # private key of wallet + ) + + # Initializing client for the private key provided. The second argument api_token is optional + await client.init(True) + + print("Account Address:", client.get_public_address()) - # Initializing client for the private key provided. The second argument api_token is optional - await client.init(True) - - print('Account Address:', client.get_public_address()) + # # gets user account data on-chain + data = await client.get_user_account_data() - # # gets user account data on-chain - data = await client.get_user_account_data() + await client.close_connections() - await client.close_connections() + pprint(data) - pprint(data) if __name__ == "__main__": - loop = asyncio.new_event_loop() - loop.run_until_complete(main()) - loop.close() \ No newline at end of file + loop = asyncio.new_event_loop() + loop.run_until_complete(main()) + loop.close() From 89c47d3c4c3f2f596b97f2c235c5e2c417182743 Mon Sep 17 00:00:00 2001 From: yasir_ejaz Date: Mon, 28 Aug 2023 06:44:12 +0400 Subject: [PATCH 11/12] Commit constants file - do not know why it got missed --- .../src/bluefin_client_sui/constants.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 bluefin-client-python-sui/src/bluefin_client_sui/constants.py diff --git a/bluefin-client-python-sui/src/bluefin_client_sui/constants.py b/bluefin-client-python-sui/src/bluefin_client_sui/constants.py new file mode 100644 index 0000000..af29f9b --- /dev/null +++ b/bluefin-client-python-sui/src/bluefin_client_sui/constants.py @@ -0,0 +1,67 @@ +Networks = { + "SUI_STAGING": { + "url": "https://fullnode.testnet.sui.io:443", + "apiGateway": "https://dapi.api.sui-staging.bluefin.io", + "socketURL": "wss://dapi.api.sui-staging.bluefin.io", + "dmsURL": "https://dapi.api.sui-staging.bluefin.io", + "webSocketURL": "wss://notifications.api.sui-staging.bluefin.io", + "onboardingUrl": "https://testnet.bluefin.io", + }, + "SUI_PROD": { + "url": "https://fullnode.testnet.sui.io:443", + "apiGateway": "https://dapi.api.sui-prod.bluefin.io", + "socketURL": "wss://dapi.api.sui-prod.bluefin.io", + "dmsURL": "https://dapi.api.sui-prod.bluefin.io", + "webSocketURL": "wss://notifications.api.sui-prod.bluefin.io", + "onboardingUrl": "https://trade.bluefin.io", + }, +} + +ORDER_FLAGS = {"IS_BUY": 1, "IS_DECREASE_ONLY": 2} + +TIME = { + "SECONDS_IN_A_MINUTE": 60, + "SECONDS_IN_A_DAY": 86400, + "SECONDS_IN_A_MONTH": 2592000, +} + +ADDRESSES = { + "ZERO": "0x0000000000000000000000000000000000000000", +} + +SERVICE_URLS = { + "MARKET": { + "ORDER_BOOK": "/orderbook", + "RECENT_TRADE": "/recentTrades", + "CANDLE_STICK_DATA": "/candlestickData", + "EXCHANGE_INFO": "/exchangeInfo", + "MARKET_DATA": "/marketData", + "META": "/meta", + "STATUS": "/status", + "SYMBOLS": "/marketData/symbols", + "CONTRACT_ADDRESSES": "/marketData/contractAddresses", + "TICKER": "/ticker", + "MASTER_INFO": "/masterInfo", + "FUNDING_RATE": "/fundingRate", + }, + "USER": { + "USER_POSITIONS": "/userPosition", + "USER_TRADES": "/userTrades", + "ORDERS": "/orders", + "GENERATE_READONLY_TOKEN": "/generateReadOnlyToken", + "ACCOUNT": "/account", + "USER_TRANSACTION_HISTORY": "/userTransactionHistory", + "AUTHORIZE": "/authorize", + "ADJUST_LEVERAGE": "/account/adjustLeverage", + "FUND_GAS": "/account/fundGas", + "TRANSFER_HISTORY": "/userTransferHistory", + "FUNDING_HISTORY": "/userFundingHistory", + "CANCEL_ON_DISCONNECT": "/dms-countdown", + }, + "ORDERS": { + "ORDERS": "/orders", + "ORDERS_HASH": "/orders/hash", + }, +} + +DAPI_BASE_NUM = 1000000000000000000 From fcf3dde870a4cadb143e25dc4d8a28b7a643b7db Mon Sep 17 00:00:00 2001 From: yasir_ejaz Date: Mon, 28 Aug 2023 06:45:34 +0400 Subject: [PATCH 12/12] Fixed repo links --- README.md | 6 +++--- pyproject.toml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 05ddaa5..20c0ead 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Python Client for the Bluefin Exchange API and SUI Contracts. ### Install -The package can be installed from [PyPi](https://pypi.org/project/bluefin-client-sui/) using pip: +The package can be installed from [PyPi](https://pypi.org/project/bluefin-v2-client-python/) using pip: ``` pip install bluefin_client_sui @@ -38,7 +38,7 @@ When initializing the client, users must accept [terms and conditions](https://b } ``` -Users can import predefined networks from [constants](https://github.com/fireflyprotocol/bluefin-client-python-sui/blob/main/src/bluefin_client_sui/constants.py): +Users can import predefined networks from [constants](https://github.com/fireflyprotocol/bluefin-v2-client-python/blob/main/src/bluefin_client_sui/constants.py): ```python from bluefin_client_sui import Networks @@ -202,7 +202,7 @@ if __name__ == "__main__": loop.close()​ ``` -Look at the [example](https://github.com/fireflyprotocol/bluefin-client-python-sui/tree/main/examples) directory to see more examples on how to use this library. +Look at the [example](https://github.com/fireflyprotocol/bluefin-v2-client-python/tree/main/examples) directory to see more examples on how to use this library. **Listening To Events Using Web Sockets:** diff --git a/pyproject.toml b/pyproject.toml index 79814bf..cdd348e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,9 +13,9 @@ dependencies = [ ] [project.urls] -"Homepage" = "https://github.com/fireflyprotocol/bluefin-client-python-sui" -"Bug Reports" = "https://github.com/fireflyprotocol/bluefin-client-python-sui/issues" -"Source" = "https://github.com/fireflyprotocol/bluefin-client-python-sui" +"Homepage" = "https://github.com/fireflyprotocol/bluefin-v2-client-python" +"Bug Reports" = "https://github.com/fireflyprotocol/bluefin-v2-client-python/issues" +"Source" = "https://github.com/fireflyprotocol/bluefin-v2-client-python" [build-system] requires = [