Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 40 additions & 13 deletions DexilonClientImpl.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json
import logging
from datetime import datetime
from typing import List

Expand All @@ -12,7 +14,7 @@
from ErrorBody import ErrorBody
from OrderErrorInfo import OrderErrorInfo
from SessionClient import SessionClient
from exceptions import DexilonAPIException, DexilonRequestException, DexilonAuthException
from exceptions import DexilonAPIException, DexilonRequestException, DexilonAuthException,DexilonErrorBodyException
from responses import AvailableSymbol, OrderBookInfo, JWTTokenResponse, OrderEvent, \
ServiceResponse, ErrorBody, AccountInfo, OrderInfo, AllOpenOrders, \
FullOrderInfo, LeverageUpdateInfo, CosmosAddressMapping
Expand Down Expand Up @@ -43,20 +45,23 @@ def __init__(self, metamask_address, api_secret):
self.headers['MetamaskAddress'] = self.METAMASK_ADDRESS
self.API_SECRET = api_secret
self.pk1 = keys.PrivateKey(bytes.fromhex(api_secret))

def setup(self):
self.client: SessionClient = SessionClient(self.API_URL, self.headers)
self.client.base_url = self.API_URL
self.dex_session_client: SessionClient = SessionClient(self.COSMOS_ADDRESS_API_URL, self.cosmos_headers)

def change_api_url(self, api_url):
def change_api_url(self, api_url, cosmos_address_api_url):
"""
Used for testing purposes

:param api_url: Public
:type api_url: str.

"""

self.API_URL = api_url
self.client.base_url = api_url
self.COSMOS_ADDRESS_API_URL = cosmos_address_api_url


def change_cosmos_api_url(self, cosmos_api_url):
"""
Expand Down Expand Up @@ -123,8 +128,8 @@ def limit_order(self, client_order_id: str, symbol: str, side: str, price: float
self.check_authentication()
json_request_body = {'clientorderId': client_order_id, 'symbol': symbol, 'side': side, 'size': size,
'price': price}
limit_order_response = self._request('POST', '/orders/limit', data=json_request_body, model=OrderEvent)
return self.parse_order_info_response(limit_order_response, 'LIMIT', client_order_id)
limit_order_response = self._request('POST', '/orders/limit', data=json_request_body, model=None)
return limit_order_response # self.parse_order_info_response(limit_order_response, 'LIMIT', client_order_id)

def cancel_all_orders(self) -> bool:
self.check_authentication()
Expand Down Expand Up @@ -170,6 +175,7 @@ def _request_dexilon_api(self, method: str, path: str, params: dict = None, data
)

def _handle_dexilon_response(self, response: dict, model: BaseModel = None) -> BaseModel:
logging.debug("response: %s" % (response))
if response is None:
service_response = parse_obj_as(ServiceResponse, response)
return service_response
Expand Down Expand Up @@ -203,10 +209,31 @@ def request_with_client(self, client, method: str, path: str, params: dict = Non
)

def _handle_response_new(self, response: dict, model: BaseModel = None) -> BaseModel:
data: dict = response['body']
logging.debug("response: %s" % (response))
data: dict = response
if data is None:
error_body: dict = response.get('errorBody')
if error_body:
raise DexilonErrorBodyException(
ErrorBody(
code=error_body.get('code'),
name=error_body.get('name'),
details=error_body.get('details', [])
)
)
else:
raise DexilonRequestException(
'body and errorBody is empty in response %s' % json.dumps(response))
service_response = parse_obj_as(ServiceResponse, response)
return service_response
if 'code' in data and data.get('code') != 200:
raise DexilonErrorBodyException(
ErrorBody(
code=data.get('code'),
name=data.get('name'),
details=data.get('details', [])
)
)
if model:
return parse_obj_as(model, data)
else:
Expand Down Expand Up @@ -247,23 +274,23 @@ def sign(self, nonce: str) -> str:
encode_defunct(nonce), private_key=self.pk1
).signature.hex()

def get_cosmos_address_mapping(self, eth_address: str):
cosmos_maping_response = self._request_dexilon_api('GET', '/registration/address_mapping/mirror/' + eth_address,
def get_cosmos_address_mapping(self, eth_address: str, chain_id: str):
cosmos_maping_response = self._request_dexilon_api('GET', '/registration/address_mapping/mirror/' + chain_id + '/' + eth_address,
model=CosmosAddressMapping)
return cosmos_maping_response

def hash_keccak(self, message: str):
return Web3.solidityKeccak(['string'], [message])
return Web3.solidity_keccak(['string'], [message])

def authenticate(self):

dexilon_address = self.get_cosmos_address_mapping(self.METAMASK_ADDRESS)
# TODO remove hardcode!
dexilon_address = self.get_cosmos_address_mapping(self.METAMASK_ADDRESS, "137")
if dexilon_address.code is not None:
print(
'There is no Dexilon chain mapping for Etherium address ' + self.METAMASK_ADDRESS + '. Registering user in Dexilon chain')
dexilon_chain_address = self.register_dexilon_user(self.METAMASK_ADDRESS)
else:
dexilon_chain_address = dexilon_address.addressMapping.cosmosAddress
dexilon_chain_address = dexilon_address.cosmosAddress

cur_time_in_milliseconds = int((datetime.utcnow() - datetime(1970, 1, 1)).total_seconds() * 1000)
nonce = str(cur_time_in_milliseconds) + '#' + dexilon_chain_address
Expand Down
5 changes: 1 addition & 4 deletions SessionClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@ def request(self, method: str, path: str, params: dict = None, data: dict = None

if not response.status_code in self.STATUS_CODES_TO_PROCESS:
errors = data.get('errors', {})
raise DexilonAPIException(
code=errors.get('code', [0])[0],
message=errors.get('message', [''])[0]
)
raise DexilonAPIException(response)

if response.status_code == 401:
raise DexilonAuthException(data)
Expand Down
12 changes: 10 additions & 2 deletions exceptions.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from responses import ErrorBody
class DexilonAPIException(Exception):

def __init__(self, response):
self.code = 0
try:
json_res = response.json()
print(json_res)
except ValueError:
self.message = 'Invalid JSON error message from Dexilon: {}'.format(response.text)
else:
self.code = json_res['errors']['code']
self.message = json_res['errors']['message']
self.code = json_res['errorBody']['code']
self.message = json_res['errorBody']['details']
self.status_code = response.status_code
self.response = response
self.request = getattr(response, 'request', None)
Expand All @@ -17,6 +19,12 @@ def __str__(self):
return 'APIError(code=%s): %s' % (self.code, self.message)


class DexilonErrorBodyException(Exception):
def __init__(self, error_body: ErrorBody):
self.error_body = error_body

def __str__(self):
return 'ErrorBodyException: %s' % self.error_body
class DexilonRequestException(Exception):
def __init__(self, message):
self.message = message
Expand Down
10 changes: 3 additions & 7 deletions responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,10 @@ class AvailableSymbol(BaseModel):
price24Percentage: Optional[float]


class AddressCosmosMapping(BaseModel):
class CosmosAddressMapping(BaseModel):
chainId: int
address: str
cosmosAddress: str


class CosmosAddressMapping(BaseModel):
addressMapping: Optional[AddressCosmosMapping]
code: Optional[int]
message: Optional[str]

Expand Down Expand Up @@ -62,8 +58,8 @@ class JWTTokenResponse(BaseModel):

class OrderEvent(BaseModel):
orderId: Optional[str]
eventType: str
event: dict
# eventType: str
# event: dict


class PositionInfo(BaseModel):
Expand Down
21 changes: 17 additions & 4 deletions tests/authentication_test.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import sys
try:
sys.path.append('/opt/dexbot3/src/')
sys.path.append('/opt/dexbot3/src/dexilon-python-sdk')
except:
print("path not exist")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А зачем этот кусок?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это кусок надо переписать, это локальный патч на случай копирования библиотеки локально в пути. В тестах это не критично, это я проверял аутентификацию

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

давай удалим из codebase?


from DexilonClientImpl import DexilonClientImpl
from OrderErrorInfo import OrderErrorInfo
from responses import FullOrderInfo


class TestAuthentication:
TEST_METAMASK_ADDRESS = '0x201d980aeD5C04a7e75860cFE29CeD9b5da05A08'
TEST_PRIVATE_KEY = '87d25c8ade8c4bb32be098bb35cd594fd1c0511c4423bf36f006f4ecd27f017c'
# TEST_METAMASK_ADDRESS = '0x201d980aeD5C04a7e75860cFE29CeD9b5da05A08'
# TEST_PRIVATE_KEY = '87d25c8ade8c4bb32be098bb35cd594fd1c0511c4423bf36f006f4ecd27f017c'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

давай удалим это?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

да, удаляем

TEST_METAMASK_ADDRESS = '0x40e42c763Dfd16EF2302c49240040a480a081C3A'
TEST_PRIVATE_KEY = '8abc5ca595d9bb6ebe133936ede1f689450baba2e5a571c2a0e356872a695b28'

def setup(self):
self.test_instance = DexilonClientImpl(self.TEST_METAMASK_ADDRESS, self.TEST_PRIVATE_KEY)
Expand All @@ -15,8 +24,7 @@ def setup(self):
def test_should_get_cosmos_address_mapping_successfully(self):
cosmos_address_maping = self.test_instance.get_cosmos_address_mapping(self.TEST_METAMASK_ADDRESS)
assert cosmos_address_maping is not None
assert cosmos_address_maping.addressMapping is not None

# TODO add asserts

def test_should_get_address_not_found_if_there_is_no_mapping(self):
cosmos_address_maping = self.test_instance.get_cosmos_address_mapping(self.TEST_METAMASK_ADDRESS + "_WRONG")
Expand Down Expand Up @@ -50,3 +58,8 @@ def test_should_reauthenticate_on_delete_order(self):
self.test_instance.headers['Authorization'] = 'Bearer + ' + self.test_instance.JWT_KEY
result = self.test_instance.cancel_order('TESTORDERID1', 'btc_usdc')
assert result

if __name__ == "__main__":
t1 = TestAuthentication()
t1.setup()
t1.test_should_get_cosmos_address_mapping_successfully()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

и зачем нам здесь main?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это тоже не надо, это патч на скорую руку когда не проходила аутентификацию. Не нужен