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/.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
new file mode 100644
index 0000000..94211d9
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,78 @@
+# 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
+
+## 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 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.
+ 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.
+
+## 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/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 65ce590..20c0ead 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,253 @@
-# bluefin-v2-client-python
-Python client v2 for Bluefin Exchange API and Smart Contracts for SUI
+
+

+
+
Bluefin Python Client Library
+
+
+
+Python Client for the Bluefin Exchange API and SUI Contracts.
+
+
+### Install
+
+The package can be installed from [PyPi](https://pypi.org/project/bluefin-v2-client-python/) 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.bluefin.io/.
+
+### Getting Started
+
+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
+{
+ "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"
+}
+```
+
+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
+```
+
+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 BluefinClient, Networks
+from pprint import pprint
+import asyncio
+
+async def main():
+ # initialize client
+ client = BluefinClient(
+ True, # agree to terms and conditions
+ Networks[TEST_NETWORK], # network to connect with
+ TEST_ACCT_KEY, # private key of wallet
+ )
+
+ # 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())
+
+ # gets user account on-chain data
+ 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:**
+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 BluefinClient, Networks
+from pprint import pprint
+import asyncio
+
+async def main():
+ # initialize client without providing private_key
+ 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")
+
+ # 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 BluefinClient, Networks, MARKET_SYMBOLS, ORDER_SIDE, ORDER_TYPE, OrderSignatureRequest
+import asyncio
+
+async def main():
+ # 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)
+
+ 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=1900, # price at which you want to place order
+ quantity=0.01, # quantity
+ side=ORDER_SIDE.SELL,
+ orderType=ORDER_TYPE.LIMIT,
+ leverage=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 BluefinClient, Networks, SOCKET_EVENTS
+import asyncio
+import time
+
+def callback(event):
+ print("Event data:", event)
+
+async def main():
+ # initialize
+ 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 bluefin 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/bluefin-v2-client-python/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 BluefinClient, Networks, SOCKET_EVENTS, MARKET_SYMBOLS
+import time
+import asyncio
+
+def callback(event):
+ print("Event data:", event)
+
+async def main():
+ # initialize
+ 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)
+
+ 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 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)
+
+ 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/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
diff --git a/examples/1.initialization.py b/examples/1.initialization.py
new file mode 100644
index 0000000..ebf65d3
--- /dev/null
+++ b/examples/1.initialization.py
@@ -0,0 +1,38 @@
+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 BluefinClient, Networks
+from pprint import pprint
+import asyncio
+
+
+async def main():
+ # 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())
+
+ # # 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()
diff --git a/examples/10.1.socket_readonly.py b/examples/10.1.socket_readonly.py
new file mode 100644
index 0000000..ea6cd88
--- /dev/null
+++ b/examples/10.1.socket_readonly.py
@@ -0,0 +1,85 @@
+import sys, os
+
+sys.path.append(os.getcwd() + "/src/")
+
+import time
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import BluefinClient, 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 = 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()
diff --git a/examples/10.sockets.py b/examples/10.sockets.py
new file mode 100644
index 0000000..eb855a0
--- /dev/null
+++ b/examples/10.sockets.py
@@ -0,0 +1,79 @@
+import sys, os
+
+sys.path.append(os.getcwd() + "/src/")
+
+import time
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import BluefinClient, 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 = BluefinClient(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 Bluefin 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()
diff --git a/examples/11.sub_accounts.py b/examples/11.sub_accounts.py
new file mode 100644
index 0000000..a7b11b0
--- /dev/null
+++ b/examples/11.sub_accounts.py
@@ -0,0 +1,61 @@
+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 (
+ 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)
+
+ clientChild = BluefinClient(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(
+ 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
+ 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()
diff --git a/examples/12.open_order_event.py b/examples/12.open_order_event.py
new file mode 100644
index 0000000..a65c53f
--- /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 (
+ BluefinClient,
+ 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: 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,
+ 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 = 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 Bluefin 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..49531be
--- /dev/null
+++ b/examples/13.orderbook_updates.py
@@ -0,0 +1,92 @@
+"""
+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 (
+ BluefinClient,
+ 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: 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
+ side=ORDER_SIDE.SELL,
+ orderType=ORDER_TYPE.LIMIT,
+ leverage=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 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 Bluefin 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..50885ce
--- /dev/null
+++ b/examples/14.web_sockets.py
@@ -0,0 +1,75 @@
+import sys, os
+
+sys.path.append(os.getcwd() + "/src/")
+import time
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import (
+ BluefinClient,
+ 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 = BluefinClient(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 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()
diff --git a/examples/15.get_funding_history.py b/examples/15.get_funding_history.py
new file mode 100644
index 0000000..dd45644
--- /dev/null
+++ b/examples/15.get_funding_history.py
@@ -0,0 +1,38 @@
+import sys, os
+
+sys.path.append(os.getcwd() + "/src/")
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import (
+ BluefinClient,
+ Networks,
+ MARKET_SYMBOLS,
+ GetFundingHistoryRequest,
+)
+from pprint import pprint
+import asyncio
+
+
+async def main():
+ 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
+ )
+
+ # 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()
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..c13ceba
--- /dev/null
+++ b/examples/16.listening_events_using_sub_account.py
@@ -0,0 +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 (
+ 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 = BluefinClient(True, Networks[TEST_NETWORK], TEST_ACCT_KEY)
+ await clientParent.init(True)
+
+ clientChild = BluefinClient(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 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)
+
+ 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()
diff --git a/examples/17.1.get_orders_readonly.py b/examples/17.1.get_orders_readonly.py
new file mode 100644
index 0000000..38a1b4e
--- /dev/null
+++ b/examples/17.1.get_orders_readonly.py
@@ -0,0 +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/")
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import (
+ BluefinClient,
+ Networks,
+ MARKET_SYMBOLS,
+ ORDER_STATUS,
+ ORDER_TYPE,
+)
+import asyncio
+
+
+async def main():
+ 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)
+
+ 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()
diff --git a/examples/17.get_orders.py b/examples/17.get_orders.py
new file mode 100644
index 0000000..a97a82c
--- /dev/null
+++ b/examples/17.get_orders.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 (
+ BluefinClient,
+ Networks,
+ MARKET_SYMBOLS,
+ ORDER_STATUS,
+ ORDER_TYPE,
+)
+import asyncio
+
+
+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))
+
+ 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()
diff --git a/examples/18.dms_api.py b/examples/18.dms_api.py
new file mode 100644
index 0000000..ab6a95a
--- /dev/null
+++ b/examples/18.dms_api.py
@@ -0,0 +1,50 @@
+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 (
+ 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)
+
+ 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..5d66925
--- /dev/null
+++ b/examples/19.Generate_readonly_token.py
@@ -0,0 +1,36 @@
+import sys, os
+
+sys.path.append(os.getcwd() + "/src/")
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import BluefinClient, Networks
+from pprint import pprint
+import asyncio
+
+
+async def main():
+ # 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)
+
+ 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()
diff --git a/examples/2.user_info.py b/examples/2.user_info.py
new file mode 100644
index 0000000..1b6b65c
--- /dev/null
+++ b/examples/2.user_info.py
@@ -0,0 +1,43 @@
+import sys, os
+
+sys.path.append(os.getcwd() + "/src/")
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import BluefinClient, Networks, MARKET_SYMBOLS
+from pprint import pprint
+import asyncio
+
+
+async def main():
+ # 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)
+
+ # 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})
+
+ # 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()
diff --git a/examples/20.contract_call.py b/examples/20.contract_call.py
new file mode 100644
index 0000000..2e01f4e
--- /dev/null
+++ b/examples/20.contract_call.py
@@ -0,0 +1,57 @@
+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 (
+ 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():
+ # 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)
+ ### 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)
+
+ # await client.withdraw_margin_from_bank(100)
+
+ # 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()
diff --git a/examples/3.balance.py b/examples/3.balance.py
new file mode 100644
index 0000000..ea204ec
--- /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 BluefinClient, Networks
+import asyncio
+
+
+async def main():
+ # 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())
+
+ # 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()
diff --git a/examples/4.placing_orders.py b/examples/4.placing_orders.py
new file mode 100644
index 0000000..1d17f97
--- /dev/null
+++ b/examples/4.placing_orders.py
@@ -0,0 +1,96 @@
+import sys, os
+
+sys.path.append(os.getcwd() + "/src/")
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import (
+ BluefinClient,
+ Networks,
+ MARKET_SYMBOLS,
+ ORDER_SIDE,
+ ORDER_TYPE,
+ OrderSignatureRequest,
+)
+import asyncio
+
+
+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)
+
+ # 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,
+ orderType=ORDER_TYPE.LIMIT,
+ leverage=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: 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,
+ )
+
+ # 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 = 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 place_limit_order(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()
diff --git a/examples/5.adjusting_leverage.py b/examples/5.adjusting_leverage.py
new file mode 100644
index 0000000..1b5638b
--- /dev/null
+++ b/examples/5.adjusting_leverage.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 BluefinClient, Networks, MARKET_SYMBOLS
+import asyncio
+
+
+async def main():
+ # 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.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)
+
+ print("Leverage on BTC market:", await client.get_user_leverage(MARKET_SYMBOLS.BTC))
+
+ # 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)
+
+ 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()
diff --git a/examples/6.adjusting_margin.py b/examples/6.adjusting_margin.py
new file mode 100644
index 0000000..d9ce6d5
--- /dev/null
+++ b/examples/6.adjusting_margin.py
@@ -0,0 +1,120 @@
+import sys, os
+
+sys.path.append(os.getcwd() + "/src/")
+
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import BluefinClient, Networks, MARKET_SYMBOLS, ADJUST_MARGIN
+import asyncio
+from bluefin_client_sui import (
+ BluefinClient,
+ Networks,
+ MARKET_SYMBOLS,
+ ORDER_SIDE,
+ ORDER_TYPE,
+ OrderSignatureRequest,
+)
+
+TEST_NETWORK = "SUI_STAGING"
+
+
+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)
+
+ # 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,
+ orderType=ORDER_TYPE.LIMIT,
+ leverage=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: 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,
+ 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 = 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())
+ await place_market_order(client)
+
+ 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)
+
+ # 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"])
+
+ # 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:", 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)
+ 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()
diff --git a/examples/7.cancelling_orders.py b/examples/7.cancelling_orders.py
new file mode 100644
index 0000000..b47f07c
--- /dev/null
+++ b/examples/7.cancelling_orders.py
@@ -0,0 +1,75 @@
+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 (
+ BluefinClient,
+ Networks,
+ MARKET_SYMBOLS,
+ ORDER_SIDE,
+ ORDER_TYPE,
+ ORDER_STATUS,
+ OrderSignatureRequest,
+)
+from pprint import pprint
+import asyncio
+
+
+async def main():
+ 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)
+
+ # 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,
+ orderType=ORDER_TYPE.LIMIT,
+ leverage=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)
+ 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"]]
+ )
+ 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()
diff --git a/examples/8.exchange_data.py b/examples/8.exchange_data.py
new file mode 100644
index 0000000..da56722
--- /dev/null
+++ b/examples/8.exchange_data.py
@@ -0,0 +1,72 @@
+import sys, os
+
+sys.path.append(os.getcwd() + "/src/")
+
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import (
+ BluefinClient,
+ Networks,
+ MARKET_SYMBOLS,
+ TRADE_TYPE,
+ Interval,
+)
+from pprint import pprint
+import asyncio
+
+
+async def main():
+ 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})
+ 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()
diff --git a/examples/9.user_data.py b/examples/9.user_data.py
new file mode 100644
index 0000000..ea9df5d
--- /dev/null
+++ b/examples/9.user_data.py
@@ -0,0 +1,70 @@
+import sys, os
+
+sys.path.append(os.getcwd() + "/src/")
+
+from config import TEST_ACCT_KEY, TEST_NETWORK
+from bluefin_client_sui import BluefinClient, Networks, MARKET_SYMBOLS, ORDER_STATUS
+from pprint import pprint
+import asyncio
+
+TEST_NETWORK = "SUI_STAGING"
+
+
+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)
+
+ # 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
+ 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()
diff --git a/examples/SocketClient.py b/examples/SocketClient.py
new file mode 100644
index 0000000..fd1f627
--- /dev/null
+++ b/examples/SocketClient.py
@@ -0,0 +1,25 @@
+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()
diff --git a/examples/SocketServer.py b/examples/SocketServer.py
new file mode 100644
index 0000000..2c9c805
--- /dev/null
+++ b/examples/SocketServer.py
@@ -0,0 +1,44 @@
+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..77f4011
--- /dev/null
+++ b/examples/config.py
@@ -0,0 +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_SUB_ACCT_KEY = "7540d48032c731b3a17947b63a04763492d84aef854246d355a703adc9b54ce9"
+TEST_NETWORK = "SUI_STAGING"
diff --git a/examples/contract_call.py b/examples/contract_call.py
new file mode 100644
index 0000000..2a632ee
--- /dev/null
+++ b/examples/contract_call.py
@@ -0,0 +1,52 @@
+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 (
+ 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():
+ # 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)
+
+ 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)
+
+ # 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()
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..cdd348e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,25 @@
+[project]
+name = "bluefin_client_sui"
+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"
+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-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 = [
+ "setuptools>=42",
+ "wheel"
+]
+build-backend = "setuptools.build_meta"
diff --git a/requirements.in b/requirements.in
new file mode 100644
index 0000000..47dfbbe
--- /dev/null
+++ b/requirements.in
@@ -0,0 +1,33 @@
+aiohttp==3.8.5
+aiosignal==1.3.1
+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
+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
+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
diff --git a/res/banner.png b/res/banner.png
new file mode 100644
index 0000000..e109051
Binary files /dev/null and b/res/banner.png differ
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/bluefin_client_sui/__init__.py b/src/bluefin_client_sui/__init__.py
new file mode 100644
index 0000000..9b2492c
--- /dev/null
+++ b/src/bluefin_client_sui/__init__.py
@@ -0,0 +1,6 @@
+from .api_service import *
+from .client import *
+from .constants import *
+from .enumerations import *
+from .interfaces import *
+from .utilities import *
diff --git a/src/bluefin_client_sui/account.py b/src/bluefin_client_sui/account.py
new file mode 100644
index 0000000..b4a8ac5
--- /dev/null
+++ b/src/bluefin_client_sui/account.py
@@ -0,0 +1,36 @@
+from .utilities import *
+import base64
+
+
+class SuiWallet:
+ def __init__(self, seed="", 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()
+
+ 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)
+
+ def getPublicKey(self):
+ return self.publicKey.ToHex()
+
+ def getPrivateKey(self):
+ return self.privateKey.ToHex()
+
+ def getUserAddress(self):
+ return self.address
diff --git a/src/bluefin_client_sui/api_service.py b/src/bluefin_client_sui/api_service.py
new file mode 100644
index 0000000..35b5a10
--- /dev/null
+++ b/src/bluefin_client_sui/api_service.py
@@ -0,0 +1,119 @@
+import json
+import aiohttp
+from .interfaces import *
+
+
+class APIService:
+ def __init__(self, url, UUID=""):
+ self.server_url = url
+ self.auth_token = None
+ self.api_token = None
+ self.uuid = UUID
+ self.client = aiohttp.ClientSession()
+
+ async def close_session(self):
+ if self.client is not None:
+ return await self.client.close()
+
+ 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.
+ """
+ url = self._create_url(service_url)
+
+ response = None
+ if auth_required:
+ response = await self.client.get(
+ 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 "",
+ },
+ )
+ else:
+ response = await self.client.get(url, params=query)
+
+ try:
+ if response.status != 503: # checking for service unavailitbility
+ return await response.json()
+ else:
+ return response
+ except:
+ raise Exception("Error while getting {}: {}".format(url, response))
+
+ 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.
+ """
+ url = self._create_url(service_url)
+ response = None
+ if auth_required:
+ headers = {
+ "Authorization": "Bearer {}".format(self.auth_token),
+ "x-mm-id": self.uuid or "",
+ }
+
+ if contentType is not "":
+ headers["Content-type"] = contentType
+
+ response = await self.client.post(url=url, data=data, headers=headers)
+ else:
+ response = await self.client.post(url=url, data=data)
+
+ try:
+ if response.status != 503: # checking for service unavailitbility
+ return await response.json()
+ else:
+ return response
+ except:
+ 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.
+ """
+ url = self._create_url(service_url)
+
+ response = None
+ if auth_required:
+ response = await self.client.delete(
+ url=url,
+ data=data,
+ 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))
+
+ """
+ Private methods
+ """
+
+ def _create_url(self, path):
+ """
+ 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
new file mode 100644
index 0000000..4b11c4a
--- /dev/null
+++ b/src/bluefin_client_sui/client.py
@@ -0,0 +1,924 @@
+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 .constants import TIME, SERVICE_URLS
+from .sockets_lib import Sockets
+from .websocket_client import WebsocketClient
+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 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", "")
+ )
+ 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"]
+
+ 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
+ """
+ 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.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.dms_api.auth_token = self.apis.auth_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):
+ """
+ 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
+ )
+ 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"]
+
+ return user_auth_token
+
+ 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 Bluefin
+ """
+ return await self.apis.post(
+ SERVICE_URLS["USER"]["AUTHORIZE"],
+ {
+ "signature": signed_hash,
+ "userAddress": self.account.address,
+ "isTermAccepted": self.are_terms_accepted,
+ },
+ )
+
+ 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
+ """
+ expiration = current_unix_timestamp()
+ # MARKET ORDER set expiration of 1 minute
+ if params["orderType"] == ORDER_TYPE.MARKET:
+ expiration += TIME["SECONDS_IN_A_MINUTE"]
+ 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),
+ )
+
+ 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
+ """
+ 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"])
+
+ 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=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=sui_params["orderType"],
+ maker=order["maker"],
+ orderbookOnly=default_value(sui_params, "orderbookOnly", True),
+ )
+
+ 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
+
+ Inputs:
+ params (OrderSignatureRequest): parameters to create cancel order with
+ parentAddress (str): Only provided by a sub account
+
+ Returns:
+ 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
+ )
+
+ 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
+
+ Inputs:
+ params (list): a list of order hashes
+ parentAddress (str): only provided by a sub account
+ Returns:
+ OrderCancellationRequest: containing symbol, hashes and signature
+ """
+ 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()
+ )
+ return OrderCancellationRequest(
+ symbol=symbol.value,
+ hashes=order_hash,
+ signature=hash_sig,
+ parentAddress=parentAddress,
+ )
+
+ async def post_cancel_order(self, params: OrderCancellationRequest):
+ """
+ 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"],
+ "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 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):
+ """
+ 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": "bluefin-python-client: {}".format(
+ default_value(params, "clientId", "bluefin-python-client")
+ ),
+ },
+ 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
+
+ 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
+ """
+ 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)
+
+ return res
+
+ 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
+ """
+
+ 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)
+
+ return res
+
+ async def withdraw_all_margin_from_bank(self):
+ bank_id = self.contracts.get_bank_id()
+ account_address = self.account.getUserAddress()
+
+ 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)
+ return res
+
+ 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 != {}:
+ 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,
+ 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"],
+ {
+ "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: 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
+ """
+
+ 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}"))
+
+ 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
+
+ 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
+
+ Returns:
+ Boolean: true if the sub account status is update
+ """
+ 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) -> float:
+ """
+ Returns user's native chain token (SUI) balance
+ """
+ try:
+ callArgs = []
+ callArgs.append(self.account.getUserAddress())
+ callArgs.append("0x2::sui::SUI")
+
+ 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}"))
+
+ def get_usdc_coins(self):
+ 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) -> float:
+ """
+ Returns user's USDC token balance on Bluefin.
+ """
+ try:
+ 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 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:
+ 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"]
+ )
+ 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.
+ 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):
+ """
+ Returns:
+ dict: all contract addresses for the all markets.
+ """
+ return await self.apis.get(SERVICE_URLS["MARKET"]["CONTRACT_ADDRESSES"])
+
+ ## 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.dms_api.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.dms_api.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
+
+ 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 _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
new file mode 100644
index 0000000..af29f9b
--- /dev/null
+++ b/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
diff --git a/src/bluefin_client_sui/contracts.py b/src/bluefin_client_sui/contracts.py
new file mode 100644
index 0000000..0ddc310
--- /dev/null
+++ b/src/bluefin_client_sui/contracts.py
@@ -0,0 +1,35 @@
+from .enumerations import MARKET_SYMBOLS
+
+
+_DEFAULT_MARKET = "BTC-PERP"
+
+
+class Contracts:
+ def __init__(self):
+ 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.contracts_global_info["SubAccounts"]["id"]
+
+ def get_bank_table_id(self):
+ return self.contracts_global_info["BankTable"]["id"]
+
+ def get_package_id(self):
+ return self.contracts_global_info["package"]["id"]
+
+ def get_bank_id(self):
+ return self.contracts_global_info["Bank"]["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"]
diff --git a/src/bluefin_client_sui/enumerations.py b/src/bluefin_client_sui/enumerations.py
new file mode 100644
index 0000000..2c512fb
--- /dev/null
+++ b/src/bluefin_client_sui/enumerations.py
@@ -0,0 +1,108 @@
+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):
+ 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"
diff --git a/src/bluefin_client_sui/interfaces.py b/src/bluefin_client_sui/interfaces.py
new file mode 100644
index 0000000..35036ad
--- /dev/null
+++ b/src/bluefin_client_sui/interfaces.py
@@ -0,0 +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
+
+
+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..4b280bc
--- /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..0b2c5c3
--- /dev/null
+++ b/src/bluefin_client_sui/order_signer.py
@@ -0,0 +1,89 @@
+from .utilities import numberToHex, hexToByteArray
+from .signer import Signer
+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
+ return flag
+
+ 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 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, "")
diff --git a/src/bluefin_client_sui/rpc.py b/src/bluefin_client_sui/rpc.py
new file mode 100644
index 0000000..7ff4752
--- /dev/null
+++ b/src/bluefin_client_sui/rpc.py
@@ -0,0 +1,136 @@
+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_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"}
+ 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)
+"""
diff --git a/src/bluefin_client_sui/signer.py b/src/bluefin_client_sui/signer.py
new file mode 100644
index 0000000..a378749
--- /dev/null
+++ b/src/bluefin_client_sui/signer.py
@@ -0,0 +1,70 @@
+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_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
+ """
+ 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()
+
+ 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.decode()
+
+ 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
diff --git a/src/bluefin_client_sui/socket_manager.py b/src/bluefin_client_sui/socket_manager.py
new file mode 100644
index 0000000..b834bb7
--- /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)
diff --git a/src/bluefin_client_sui/sockets_lib.py b/src/bluefin_client_sui/sockets_lib.py
new file mode 100644
index 0000000..3a4f2f0
--- /dev/null
+++ b/src/bluefin_client_sui/sockets_lib.py
@@ -0,0 +1,209 @@
+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): Bluefin onboarding token.
+ """
+ self.token = token
+
+ def set_api_token(self, 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
+ """
+ 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 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,
+ },
+ ],
+ )
+
+ 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 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,
+ },
+ ],
+ )
+ 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..2f89d7f
--- /dev/null
+++ b/src/bluefin_client_sui/utilities.py
@@ -0,0 +1,127 @@
+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 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",
+ )
diff --git a/src/bluefin_client_sui/websocket_client.py b/src/bluefin_client_sui/websocket_client.py
new file mode 100644
index 0000000..3544232
--- /dev/null
+++ b/src/bluefin_client_sui/websocket_client.py
@@ -0,0 +1,220 @@
+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): Bluefin onboarding token.
+ """
+ self.token = token
+
+ def set_api_token(self, 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
+ """
+ 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 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,
+ },
+ ],
+ ]
+ )
+ )
+ )
+ 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 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,
+ },
+ ],
+ ]
+ )
+ )
+ )
+ 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..0c62fff
--- /dev/null
+++ b/src/check.py
@@ -0,0 +1,33 @@
+from bluefin_exchange_client_sui import BluefinClient, Networks
+from pprint import pprint
+import asyncio
+
+TEST_ACCT_KEY = "!#12"
+TEST_NETWORK = "SUI_STAGING"
+
+
+async def main():
+ # 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())
+
+ # # 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()