diff --git a/.gitignore b/.gitignore
index 37e607ed..5201369f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,7 @@ __pycache__
venv
_virtualenv
+node_modules
+package.json
+yarn.lock
+
diff --git a/lib/pymaker b/lib/pymaker
index 894fd885..5cfacf5f 160000
--- a/lib/pymaker
+++ b/lib/pymaker
@@ -1 +1 @@
-Subproject commit 894fd8858943f19b227a16ac6a96cd0932cf32cd
+Subproject commit 5cfacf5f21638a62c86ad7217002fa62d6eb0027
diff --git a/pyexchange/abi/LEVERJ.abi b/pyexchange/abi/LEVERJ.abi
new file mode 100644
index 00000000..a6acc0dc
--- /dev/null
+++ b/pyexchange/abi/LEVERJ.abi
@@ -0,0 +1 @@
+[{"constant":false,"inputs":[],"name":"switchOff","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"},{"name":"entryHash","type":"bytes32"}],"name":"proveInConfirmedBalances","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"nonceGenerator","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"entryHash","type":"bytes32"},{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"}],"name":"exit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"addresses","type":"address[]"},{"name":"uints","type":"uint256[]"},{"name":"leaves","type":"bytes32[]"},{"name":"indexes","type":"uint256[]"},{"name":"predecessor","type":"bytes"},{"name":"successor","type":"bytes"}],"name":"reclaimDeposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"}],"name":"removeOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isOn","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"},{"name":"asset","type":"address"}],"name":"hasExited","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"},{"name":"leaf","type":"bytes32"},{"name":"index","type":"uint256"}],"name":"checkProofOrdered","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"visibilityDelay","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"},{"name":"quantity","type":"uint256"}],"name":"depositToken","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"withdrawn","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"},{"name":"leaf","type":"bytes32"},{"name":"index","type":"uint256"}],"name":"verifyIncludedAtIndex","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"deposits","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"submissionBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"exited","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"gblocksByNumber","outputs":[{"name":"number","type":"uint256"},{"name":"withdrawalsRoot","type":"bytes32"},{"name":"depositsRoot","type":"bytes32"},{"name":"balancesRoot","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"addresses","type":"address[]"},{"name":"uints","type":"uint256[]"},{"name":"signature","type":"bytes"},{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"operator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"},{"name":"leaf","type":"bytes32"}],"name":"verifyIncluded","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"},{"name":"entryHash","type":"bytes32"}],"name":"proveInUnconfirmedBalances","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"}],"name":"addOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"registry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"confirmationDelay","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ETH","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"root","type":"bytes32"}],"name":"includesWithdrawals","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"gblocksByBalancesRoot","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"entryHash","type":"bytes32"}],"name":"canExit","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"depositEther","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"},{"name":"entryHash","type":"bytes32"}],"name":"proveInConfirmedWithdrawals","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOwners","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"gblocksByWithdrawalsRoot","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"root","type":"bytes32"}],"name":"isConfirmedWithdrawals","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"},{"name":"leaf","type":"bytes32"}],"name":"checkProof","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"gblockNumber","type":"uint256"},{"name":"withdrawalsRoot","type":"bytes32"},{"name":"depositsRoot","type":"bytes32"},{"name":"balancesRoot","type":"bytes32"}],"name":"submit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"asset","type":"address"},{"name":"quantity","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"designatedGblock","type":"uint256"}],"name":"reclaimDepositOnHalt","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"currentGblockNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"submissionInterval","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"root","type":"bytes32"}],"name":"isUnconfirmedWithdrawals","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"addresses","type":"address[]"},{"name":"uints","type":"uint256[]"},{"name":"signature","type":"bytes"},{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"}],"name":"exitOnHalt","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"root","type":"bytes32"}],"name":"isUnconfirmedBalances","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"number","type":"uint256"}],"name":"isConfirmedGblock","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"gblocksByDepositsRoot","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"addresses","type":"address[]"},{"name":"uints","type":"uint256[]"},{"name":"signature","type":"bytes"},{"name":"proof","type":"bytes"},{"name":"root","type":"bytes32"}],"name":"claimExit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"canSubmit","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_registry","type":"address"},{"name":"_operator","type":"address"},{"name":"_submissionInterval","type":"uint256"},{"name":"_version","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"custodian","type":"address"},{"indexed":true,"name":"account","type":"address"},{"indexed":true,"name":"asset","type":"address"},{"indexed":false,"name":"quantity","type":"uint256"},{"indexed":false,"name":"nonce","type":"uint256"},{"indexed":false,"name":"designatedGblock","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"custodian","type":"address"},{"indexed":true,"name":"account","type":"address"},{"indexed":true,"name":"asset","type":"address"},{"indexed":false,"name":"quantity","type":"uint256"},{"indexed":false,"name":"nonce","type":"uint256"}],"name":"DepositReclaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"hash","type":"bytes32"},{"indexed":true,"name":"account","type":"address"},{"indexed":true,"name":"asset","type":"address"},{"indexed":false,"name":"quantity","type":"uint256"},{"indexed":false,"name":"timestamp","type":"uint256"},{"indexed":false,"name":"confirmationThreshold","type":"uint256"}],"name":"ExitClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":true,"name":"asset","type":"address"},{"indexed":false,"name":"quantity","type":"uint256"}],"name":"Exited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"hash","type":"bytes32"},{"indexed":true,"name":"account","type":"address"},{"indexed":true,"name":"asset","type":"address"},{"indexed":false,"name":"quantity","type":"uint256"}],"name":"Withdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"gblockNumber","type":"uint256"},{"indexed":false,"name":"withdrawalsRoot","type":"bytes32"},{"indexed":false,"name":"depositsRoot","type":"bytes32"},{"indexed":false,"name":"balancesRoot","type":"bytes32"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[],"name":"Off","type":"event"}]
diff --git a/pyexchange/abi/TOKEN_ABI.abi b/pyexchange/abi/TOKEN_ABI.abi
new file mode 100644
index 00000000..425543f8
--- /dev/null
+++ b/pyexchange/abi/TOKEN_ABI.abi
@@ -0,0 +1,45 @@
+[
+ {
+ "constant": true, "inputs": [], "name": "totalSupply", "outputs": [{"name": "", "type": "uint256"}],
+ "payable" : false, "stateMutability": "view", "type": "function"
+ },
+ {
+ "anonymous": false, "inputs": [
+ {"indexed": true, "name": "_from", "type": "address"}, {"indexed": true, "name": "_to", "type": "address"},
+ {"indexed": false, "name": "_value", "type": "uint256"}
+ ], "name" : "Transfer", "type": "event"
+ },
+ {
+ "anonymous": false, "inputs": [
+ {"indexed": true, "name": "_owner", "type": "address"}, {"indexed": true, "name": "_spender", "type": "address"},
+ {"indexed": false, "name": "_value", "type": "uint256"}
+ ], "name" : "Approval", "type": "event"
+ },
+ {
+ "constant": true, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf",
+ "outputs" : [{"name": "balance", "type": "uint256"}], "payable": false, "stateMutability": "view",
+ "type" : "function"
+ },
+ {
+ "constant" : false, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}],
+ "name" : "transfer", "outputs": [{"name": "success", "type": "bool"}], "payable": false,
+ "stateMutability": "nonpayable", "type": "function"
+ },
+ {
+ "constant" : false, "inputs": [
+ {"name": "_from", "type": "address"}, {"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}
+ ], "name" : "transferFrom", "outputs": [{"name": "success", "type": "bool"}], "payable": false,
+ "stateMutability": "nonpayable", "type": "function"
+ },
+ {
+ "constant" : false,
+ "inputs" : [{"name": "_spender", "type": "address"}, {"name": "_value", "type": "uint256"}],
+ "name" : "approve", "outputs": [{"name": "success", "type": "bool"}], "payable": false,
+ "stateMutability": "nonpayable", "type": "function"
+ },
+ {
+ "constant" : true, "inputs": [{"name": "_owner", "type": "address"}, {"name": "_spender", "type": "address"}],
+ "name" : "allowance", "outputs": [{"name": "remaining", "type": "uint256"}], "payable": false,
+ "stateMutability": "view", "type": "function"
+ }
+]
diff --git a/pyexchange/leverj.py b/pyexchange/leverj.py
new file mode 100644
index 00000000..95783e7a
--- /dev/null
+++ b/pyexchange/leverj.py
@@ -0,0 +1,412 @@
+# This file is part of Maker Keeper Framework.
+#
+# Copyright (C) 2019 grandizzy
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+import logging
+from pyexchange.api import PyexAPI
+import dateutil.parser
+import time
+import requests
+import json
+from leverj_ordersigner import *
+from pprint import pformat
+from pymaker import Contract, Address, Transact, Wad
+from pymaker.util import http_response_summary, bytes_to_hexstring
+from typing import Optional
+from pymaker.sign import eth_sign, to_vrs
+from web3 import Web3
+from typing import Optional, List
+import urllib.request
+
+
+class Order:
+ def __init__(self,
+ order_id: str,
+ pair: str,
+ is_sell: bool,
+ price: Wad,
+ amount: Wad):
+
+ assert(isinstance(order_id, str))
+ assert(isinstance(pair, str))
+ assert(isinstance(is_sell, bool))
+ assert(isinstance(price, Wad))
+ assert(isinstance(amount, Wad))
+
+ self.order_id = order_id
+ self.pair = pair
+ self.is_sell = is_sell
+ self.price = price
+ self.amount = amount
+
+ @property
+ def sell_to_buy_price(self) -> Wad:
+ return self.price
+
+ @property
+ def buy_to_sell_price(self) -> Wad:
+ return self.price
+
+ @property
+ def remaining_buy_amount(self) -> Wad:
+ return self.amount*self.price if self.is_sell else self.amount
+
+ @property
+ def remaining_sell_amount(self) -> Wad:
+ return self.amount if self.is_sell else self.amount*self.price
+
+ def __repr__(self):
+ return pformat(vars(self))
+
+ @staticmethod
+ def from_list(item: list, pair: str):
+ return Order(order_id=item['uuid'],
+ pair=pair,
+ is_sell=True if item['side'] == 'sell' else False,
+ price=Wad.from_number(item['price']),
+ amount=Wad.from_number(item['quantity']))
+
+
+
+class Trade:
+ def __init__(self,
+ trade_id: Optional[id],
+ timestamp: int,
+ pair: str,
+ is_sell: bool,
+ price: Wad,
+ amount: Wad):
+ assert(isinstance(trade_id, int) or (trade_id is None) or isinstance(trade_id, str))
+ assert(isinstance(timestamp, int))
+ assert(isinstance(pair, str))
+ assert(isinstance(is_sell, bool))
+ assert(isinstance(price, Wad))
+ assert(isinstance(amount, Wad))
+
+ self.trade_id = trade_id
+ self.timestamp = timestamp
+ self.pair = pair
+ self.is_sell = is_sell
+ self.price = price
+ self.amount = amount
+
+ def __eq__(self, other):
+ assert(isinstance(other, Trade))
+ return self.trade_id == other.trade_id and \
+ self.timestamp == other.timestamp and \
+ self.pair == other.pair and \
+ self.is_sell == other.is_sell and \
+ self.price == other.price and \
+ self.amount == other.amount
+
+ def __hash__(self):
+ return hash((self.trade_id,
+ self.timestamp,
+ self.pair,
+ self.is_sell,
+ self.price,
+ self.amount))
+
+ def __repr__(self):
+ return pformat(vars(self))
+
+ @staticmethod
+ def from_our_list(pair, trade):
+ return Trade(trade_id=trade['trade_id'],
+ timestamp=int(dateutil.parser.parse(trade['created_at']).timestamp()),
+ pair=pair,
+ is_sell=True if trade['side'] == 'sell' else False,
+ price=Wad.from_number(trade['price']),
+ amount=Wad.from_number(trade['size']))
+
+ @staticmethod
+ def from_all_list(pair, trade):
+ return Trade(trade_id=trade['executionid'],
+ timestamp=int(int(trade['eventTime'])/1000000),
+ pair=pair,
+ is_sell=True if trade['side'] == 'sell' else False,
+ price=Wad.from_number(trade['price']),
+ amount=Wad.from_number(trade['quantity']))
+
+
+
+
+class LeverjAPI(PyexAPI):
+ """LeverJ API interface.
+ """
+
+ logger = logging.getLogger()
+
+ def __init__(self, web3: Web3, api_server: str, account_id: str, api_key: str, api_secret: str, timeout: float):
+ assert(isinstance(api_key, str))
+ assert(isinstance(api_secret, str))
+ assert(isinstance(account_id, str))
+
+ self.web3 = web3
+
+ self.api_server = api_server
+ self.api_key = api_key
+ self.api_secret = api_secret
+ self.account_id = account_id
+ self.timeout = timeout
+
+ def get_account(self):
+ return self._http_authenticated("GET", "/api/v1", "/account", None)
+
+ def get_balances(self):
+ return self._http_authenticated("GET", "/api/v1", "/account/balance", None)
+
+ def get_balance(self, coin: str):
+ assert(isinstance(coin, str))
+ balances = self.get_balances()
+ for key in balances:
+ if balances[key]['symbol'] == coin:
+ return balances[key]['plasma']
+
+
+ def get_config(self):
+ return self._http_authenticated("GET", "/api/v1", "/all/config", None)
+
+ def get_custodian_address(self):
+ config = self.get_config()
+ return config['config']['network']['custodian']
+
+ def get_product(self, pair: str):
+ assert(isinstance(pair, str))
+ return self.get_config()['instruments'][pair]
+
+ def get_info(self):
+ return self._http_authenticated("GET", "/api/v1", "/all/info", None)
+
+ def get_all_orders(self):
+ return self._http_authenticated("GET", "/api/v1", "/order", None)
+
+ def get_orders(self, pair: str) -> List[Order]:
+ assert(isinstance(pair, str))
+ result_pair = []
+ result = self._http_authenticated("GET", "/api/v1", "/order", None)
+ for item in result:
+ if item['instrument'] == pair:
+ result_pair.append(item)
+ return list(map(lambda item: Order.from_list(item, pair), result_pair))
+
+ def get_trades(self, pair: str, count: int) -> List[Trade]:
+ assert(isinstance(count, int))
+ assert(isinstance(pair, str))
+ result_pair = []
+ result = self._http_authenticated("GET", "/api/v1", f"/account/execution?count={count}", None)
+ for item in result:
+ if item['instrument'] == pair:
+ result_pair.append(item)
+
+ return list(map(lambda item: Trade.from_all_list(pair, item), result_pair))
+
+ def get_symbol_trades(self, symbol):
+ return self._http_authenticated("GET", "/api/v1", f"/instrument/{symbol}/trade", None)
+
+ def get_orderbook_symbol(self, symbol):
+ return self._http_authenticated("GET", "/api/v1", f"/instrument/{symbol}/orderbook", None)
+
+ def sign_order(self, order, orderInstrument):
+ return run_js('compute_signature_for_exchange_order', {'order': order, 'instrument': orderInstrument, 'signer': self.api_secret})
+
+ def createNewOrder(self, side, price, quantity, orderInstrument):
+ order = {
+ 'orderType': 'LMT',
+ 'side': side,
+ 'price': price,
+ 'quantity': quantity,
+ 'timestamp': int(time.time()*1000000),
+ 'accountId': self.account_id,
+ 'token': orderInstrument['quote']['address'],
+ 'instrument': orderInstrument['symbol']
+ }
+ order['signature'] = self.sign_order(order, orderInstrument)['signature']
+
+ return order
+
+ def place_order(self, order):
+ return self._http_authenticated("POST", "/api/v1", "/order", [order])
+
+ def cancel_order(self, order_id: str) -> bool:
+ assert(isinstance(order_id, str))
+
+ result = self._http_authenticated("DELETE", "/api/v1", f"/order/{order_id}", None)
+
+ if order_id != result[0][0]:
+ return False
+
+ return True
+
+ def cancel_all_orders(self) -> List:
+ result = []
+
+ orders = self.get_all_orders()
+
+ for order in orders:
+ order_id = order['uuid']
+ result.append(self.cancel_order(order_id))
+
+ return result
+
+
+
+ def _http_authenticated(self, method: str, api_path: str, resource: str, body):
+ assert(isinstance(method, str))
+ assert(isinstance(api_path, str))
+ assert(isinstance(resource, str))
+ assert(isinstance(body, dict) or (body is None) or (body, list))
+
+ data = json.dumps(body, separators=(',', ':'))
+ nonce = int(time.time()*1000)
+ #print(nonce)
+
+ params = {
+ 'method': method,
+ 'uri': resource,
+ 'nonce': nonce
+ }
+
+
+ if body is not None:
+ params['body'] = body
+
+ payload = str(nonce)
+ signature = self._create_signature(payload)
+
+ v, r, s = to_vrs(signature)
+
+ auth_header = f"NONCE {self.account_id}.{self.api_key}"\
+ f".{v}"\
+ f".{bytes_to_hexstring(r)}"\
+ f".{bytes_to_hexstring(s)}"
+
+ headers={ "Authorization": auth_header, "Nonce": str(nonce) }
+ if body is not None:
+ headers["Content-Type"] = "application/json"
+
+
+ return self._result(requests.request(method=method,
+ url=f"{self.api_server}{api_path}{resource}",
+ data=data,
+ headers=headers,
+ timeout=self.timeout))
+
+ def _create_signature(self, params: str) -> str:
+ assert(isinstance(params, str))
+
+ return eth_sign(bytes(params, 'utf-8'), self.web3, self.api_secret)
+
+ def _result(self, result) -> Optional[dict]:
+ if not result.ok:
+ raise Exception(f"Leverj API invalid HTTP response: {http_response_summary(result)}")
+
+ try:
+ data = result.json()
+ except Exception:
+ raise Exception(f"Leverj API invalid JSON response: {http_response_summary(result)}")
+
+ return data
+
+class LeverJ(Contract):
+ """A client for the Leverj proxy exchange contract.
+
+ Attributes:
+ web3: An instance of `Web` from `web3.py`.
+ address: Ethereum address of the `Leverj` custodian contract.
+ """
+
+
+
+
+ abi = Contract._load_abi(__name__, 'abi/LEVERJ.abi')
+ token_abi = Contract._load_abi(__name__, 'abi/TOKEN_ABI.abi')
+ #print(abi)
+
+ def __init__(self, web3: Web3, address: Address):
+ assert(isinstance(web3, Web3))
+ assert(isinstance(address, Address))
+
+ self.web3 = web3
+ self.address = address
+ self._contract = self._get_contract(web3, self.abi, address)
+
+
+ def approve_token(self, token_address: str, amount: int) -> Transact:
+ token_contract = self._get_contract(self.web3, self.token_abi, Address(token_address))
+ return Transact(self, self.web3, self.token_abi, Address(token_address), token_contract, "approve",[self.web3.eth.defaultAccount, amount], {})
+
+ def deposit_token(self, token_address, to, amount: int) -> Transact:
+ token_contract = self._get_contract(self.web3, self.token_abi, Address(token_address))
+ return Transact(self, self.web3, self.token_abi, Address(token_address), token_contract, "transferFrom",[self.web3.eth.defaultAccount, to, amount], {})
+
+ def deposit_ether(self, amount: Wad) -> Transact:
+ return Transact(self, self.web3, self.abi, self.address, self._contract, "depositEther",[], {'value': amount.value})
+
+
+
+ def withdraw_token(self, leverjobj: LeverjAPI, token_addr: str, quantity: int) -> int:
+ assert(isinstance(leverjobj, LeverjAPI))
+ assert(isinstance(token_addr, str))
+ assert(isinstance(quantity, int))
+
+ ethereum_account = leverjobj.account_id
+ custodian_account = self.address
+ timestamp = int(time.time()*1000)
+ api_secret = leverjobj.api_secret
+ sha3_hash = Web3.soliditySha3(['string','string','uint256','uint256'],[ethereum_account, token_addr, quantity, timestamp])
+ signature = eth_sign(sha3_hash, leverjobj.web3, api_secret, True)
+ payload = {
+ 'asset': token_addr,
+ 'quantity': quantity,
+ 'timestamp': timestamp,
+ 'signature': signature
+ }
+ leverjobj._http_authenticated("POST", "/api/v1", "/account/withdraw", payload)
+ number_dict = leverjobj._http_authenticated("GET", "/api/v1", f"/plasma/{custodian_account}", None)
+ return number_dict['number']+3
+
+
+ def claim_funds(self, leverjobj: LeverjAPI, asset: str, quantity: int) -> Transact:
+ assert(isinstance(leverjobj, LeverjAPI))
+ assert(isinstance(asset, str))
+ assert(isinstance(quantity, int))
+
+ gluon_block_number = self.withdraw_token(leverjobj, asset, quantity)
+ ethereum_account = leverjobj.account_id
+ custodian_account = self.address
+ print(
+ f"ethereum_account: {ethereum_account}, custodian_account: {custodian_account}, asset: {asset}")
+ response = leverjobj._http_authenticated(
+ "GET", "/api/v1", f"/plasma/{custodian_account}/evmparams/withdrawals/account/{ethereum_account}/asset/{asset}", None)
+ print(f"response: {response} ")
+ addresses = response['addresses']
+ uints_str = response['uints']
+ uints = [int(i) for i in uints_str]
+ signature = response['signature']
+ proof = response['proof']
+ root = response['root']
+ print(addresses,uints,signature,proof,root)
+ while (leverjobj._http_authenticated("GET", "/api/v1", f"/plasma/{custodian_account}", None)['number'] < gluon_block_number):
+ print(f'current gluon block is {leverjobj._http_authenticated("GET", "/api/v1", f"/plasma/{custodian_account}", None)["number"]} and the gluon block we are waiting for is {gluon_block_number}')
+ time.sleep(30)
+
+ return Transact(self, self.web3, self.abi, self.address, self._contract, "withdraw",[addresses, uints, signature, proof, root], {})
+
+
+
+
+
diff --git a/tests/manual_test_leverj.py b/tests/manual_test_leverj.py
new file mode 100644
index 00000000..021b4a37
--- /dev/null
+++ b/tests/manual_test_leverj.py
@@ -0,0 +1,182 @@
+# This file is part of Maker Keeper Framework.
+#
+# Copyright (C) 2017-2018 reverendus
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+import sys
+import base64
+import web3
+#from pyexchange.coinbase import CoinbaseApi
+from web3 import Web3
+from pyexchange.leverj import LeverjAPI, LeverJ
+from pymaker import Wad, Address
+from pymaker.keys import register_private_key
+import time
+import urllib.request
+import json
+
+#w3 = Web3(Web3.HTTPProvider("http://vps-20270-1740-t2.tilaa.cloud:8545/", request_kwargs={'timeout': 60}))
+w3 = Web3(Web3.HTTPProvider("https://ropsten.infura.io/v3/351c78ed30774d93b247c588ec019a34", request_kwargs={'timeout': 60}))
+w3.eth.defaultAccount = "0x3b0194e96c57dd9cFb839720080b5626718C0E48"
+#w3.eth.defaultAccount = "0xE239Caeb4A6eCe2567fa5307f6b5D95149a5188F"
+register_private_key(w3, "DC2CBC1873E77E824E538B8CD591268831759BBBDE2CC6C56076379552899BA5")
+#register_private_key(w3, "D6D1743A72FF5CECEB3D4DD0C57BAF919BCCFB90CA52D945C6B94E609A61D2D6")
+TEST_LEV_ADDRESS='0xAa7127e250E87476FdD253f15e86A4Ea9c4c4BD4'
+TEST_DAI_ADDRESS='0xb0F776EB352738CF25710d92Fca2f4A5e2c24D3e'
+TEST_ETH_ADDRESS='0x0000000000000000000000000000000000000000'
+
+#coinbase = CoinbaseApi("https://api.pro.coinbase.com", sys.argv[1], sys.argv[2], sys.argv[3], 9.5)
+leverj = LeverjAPI(w3, "https://test.leverj.io", "0x3b0194e96c57dd9cFb839720080b5626718C0E48","0x376E7631ef8ABd2685904bB5Ab16Cd8D2C51E862","0xf41c425f1a6b9b0e57ebad73cc01a65a2c9c2acc5f3327acf8930f0dbc74f230" ,9.5)
+
+#custodian_address = Address(leverj.get_custodian_address())
+custodian_address = Address("0xD5727f9d8C5b9E4472566683F4e562Ef9B47dCE3")
+
+
+leverj_custodian = LeverJ(w3, custodian_address)
+
+#print(leverj_custodian.withdraw_token(leverj, TEST_LEV_ADDRESS, 400000))
+#claim_funds = leverj_custodian.claim_funds(leverj, TEST_ETH_ADDRESS,400000000).transact()
+#print(claim_funds.transaction_hash.hex())
+#print(claim_funds.successful)
+
+
+first_approval = leverj_custodian.approve_token(TEST_DAI_ADDRESS, 400000).transact()
+print(first_approval.__dict__)
+print(first_approval.transaction_hash.hex())
+print("above is about first approval")
+print(" ")
+print("Now we will send some LEV/DAI to custodian contract")
+first_lev_deposit = leverj_custodian.deposit_token(TEST_DAI_ADDRESS, custodian_address.address, 400000).transact()
+print(first_lev_deposit.successful)
+print(first_lev_deposit.transaction_hash.hex())
+print("above is the first lev deposit to custodian contract")
+#
+#
+#
+#first_transact = leverj_custodian.deposit_ether(Wad.from_number(0.01)).transact()
+#print(first_transact.successful)
+#print(first_transact.transaction_hash.hex())
+#second_transact = leverj_custodian.deposit_ether(Wad.from_number(0.02)).transact()
+#print(second_transact.successful)
+#print(second_transact.transaction_hash.hex())
+#
+
+
+print("get balances")
+print(leverj.get_balances())
+
+print("get balances for ETH")
+print(leverj.get_balance("ETH"))
+
+print("get balances for DAI")
+print(leverj.get_balance("DAI"))
+
+print("getting LEVETH instrument from get_product")
+print(leverj.get_product("LEVETH"))
+
+
+print("getting config")
+result = leverj.get_config()
+
+instruments = result['instruments']
+LEVETH_instrument = instruments['LEVETH']
+
+print(result)
+
+print("get custodian address for either mainnet or ropsten")
+print(leverj.get_custodian_address())
+
+print("printing LEVETH_instrument")
+print(LEVETH_instrument)
+print("printing LEVETH_instrument keys")
+print(LEVETH_instrument.keys())
+print("creating new order in testing")
+newOrder = leverj.createNewOrder('buy', 0.001229, 20, LEVETH_instrument)
+tradeNewOrder = leverj.createNewOrder('buy', 0.0017145, 5, LEVETH_instrument)
+
+print(newOrder)
+
+print('sending order to test leverj')
+leverj.place_order(newOrder)
+
+print('sending aggressive order to trade')
+leverj.place_order(tradeNewOrder)
+
+#try:
+# leverj.post_order(newOrder)
+#except:
+# print("there was an issue sending orders")
+# print("error", sys.exc_info()[0], "occurred.")
+
+print("orders on the platform test")
+print(leverj.get_all_orders())
+
+print("orders from LEVETH")
+print(leverj.get_orders("LEVETH"))
+
+print("executions")
+print(leverj.get_trades("LEVETH",3))
+print(len(leverj.get_trades("LEVETH",3)))
+print("7 executions")
+print(leverj.get_trades("LEVETH",7))
+
+time.sleep(15)
+
+orders = leverj.get_all_orders()
+
+#for order in orders:
+# order_id = order['uuid']
+# print(leverj.cancel_order(order_id))
+
+print("cancelling all orders using cancel_all_orders function")
+print(leverj.cancel_all_orders())
+
+
+
+
+
+
+
+
+# print("get orders")
+# print(coinbase.get_balances())
+# # print("get balance ETH")
+# print(coinbase.get_balance("USDC"))
+# print("get balance ETH")
+# print(coinbase.get_balance("BTC"))
+# print("cancel orders")
+# print(coinbase.cancel_all_orders())
+# print("cancel orders")
+# print(coinbase.cancel_order("144c6f8e-713f-4682-8435-5280fbe8b2b4"))
+# print("place orders")
+# order_id = coinbase.place_order("ETH-USDC", True, Wad.from_number(120), round(Wad.from_number(0.0156547676576), 8))
+# print("cancel order")
+# print(coinbase.cancel_order(order_id))
+# order_id = coinbase.place_order("ETH-USDC", False, Wad.from_number(90), round(Wad.from_number(0.01), 8))
+# print("place orders")
+# order_id = coinbase.place_order("ETH-USDC", False, Wad.from_number(90.11111111111), Wad.from_number(0.0156547676576))
+# print(coinbase.cancel_order(order_id))
+#print(coinbase.get_trades("ETH-USDC"))
+# print("place orders")
+# order_id = coinbase.place_order("ETH-USDC", False, Wad.from_number(20.11111111111), Wad.from_number(0.0156547676576))
+# print(order_id)
+# print("get orders")
+# print(coinbase.get_orders("ETH-USDC"))
+# print("cancel orders")
+# print(coinbase.cancel_all_orders())
+# print("get trades")
+# print(coinbase.get_trades("ETH-USDC"))
+
+