diff --git a/.gitignore b/.gitignore index 37e607ed..00b652ea 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,8 @@ __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..64032484 --- /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_exchange_python_bridge 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..7a1d7012 --- /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")) + +