def test_wallet_reload_sequence(self, private_key, env): wallet = Wallet(private_key=private_key, env=env) wallet.initialise_wallet() account_sequence = wallet.sequence wallet.increment_account_sequence() wallet.reload_account_sequence() assert wallet.sequence == account_sequence
class Binance(): def __init__(self): self.env = BinanceEnvironment.get_testnet_env() self.client = HttpApiClient(env=self.env) with open('secret/bnb_key.txt', 'r') as f: key = f.readline() self.wallet = Wallet(key, env=self.env) self.address = self.wallet.address driver_logger.info(f'bnb address: {self.address}') def thor_swap(self, i_symbol, amount, to_address, memo): transfer_msg = TransferMsg( wallet=self.wallet, symbol=i_symbol, amount=amount, to_address=to_address, memo=memo ) res = self.client.broadcast_msg(transfer_msg, sync=True) tx_hash = res[0]['hash'] driver_logger.debug(f'boardcasted hash {tx_hash}') self.wallet.reload_account_sequence() return tx_hash
class BinanceApi: def __init__(self, key, test=False): self.BUSD = 'BUSD-BD1' self.BNB = 'BNB' self.pairs = [] self.bnb_pairs = {} self.busd_pairs = {} self.api_instance = binance_client.DefaultApi() if test: self.env = BinanceEnvironment.get_testnet_env() self.RUNE = 'RUNE-67C' self.api_instance.api_client.configuration.host = self.env.api_url + '/api' else: self.env = BinanceEnvironment(api_url='https://dex-european.binance.org', wss_url='wss://dex.binance.org/api/', hrp='bnb') self.RUNE = 'RUNE-B1A' binance_logger.info(f'Binance connected to node: {self.env.api_url}') self.client = HttpApiClient(env=self.env) self.wallet = Wallet(private_key=key, env=self.env) self.wallet.reload_account_sequence() self.account_info() def parse_market(self): offset = 0 try: logging.info("parsing market pairs") while True: pairs = self.api_instance.get_pairs(limit=500, offset=offset) for pair in pairs: self.pairs.append(pair) if pair["quote_asset_symbol"] == self.BNB: self.bnb_pairs[pair["base_asset_symbol"]] = pair["lot_size"] elif pair["quote_asset_symbol"] == self.BUSD: self.busd_pairs[pair["base_asset_symbol"]] = pair["lot_size"] offset += 500 time.sleep(1) except ApiException as e: if e.reason == "Bad Request": logging.info("parsing finished, %s market pairs" % len(self.pairs)) logging.debug("bnb pairs: %s" % self.bnb_pairs) logging.debug("busd pairs: %s" % self.busd_pairs) else: logging.info("Exception when calling DefaultApi->getPairs: %s\n" % e) def depth(self, symbol, bnb=True): if bnb: pair = symbol + '_' + self.BNB else: pair = symbol + '_' + self.BUSD try: depth = self.api_instance.get_depth(symbol=pair, limit=5) binance_logger.debug(f'{pair} market depth bids:{depth.bids[0]} asks:{depth.asks[0]}') return depth except ApiException as e: if e.reason == 'Gateway Time-out': logging.info("cloudfront error") time.sleep(5) logging.info("recalling get_depth()") return self.depth(symbol=pair, bnb=bnb) binance_logger.debug(f'Exception when calling get_depth() {e}') def account_info(self): try: account_info = self.api_instance.get_account(self.wallet.address) binance_logger.info(f'account info: {account_info}') return account_info except ApiException as e: binance_logger.debug(f'Exception when calling get_account() {e}') # tracking = list(filter(lambda coin: coin['symbol'] == self.pairs[token], api_response.balances)) # pnl = (float(tracking[0]['free']) - float(self.balances[token])) / float(self.balances[token]) # self.balances[token] = float(tracking[0]['free']) def get_balance(self): try: account_info = self.api_instance.get_account(self.wallet.address) balance_info = account_info.balances binance_logger.info(f'balance info: {balance_info}') binance_logger.info(f'sequence number: {account_info.sequence}') return balance_info except ApiException as e: binance_logger.debug(f'Exception when calling get_account() {e}') def binance_check_hash(self, hash): while True: try: api_response = self.api_instance.get_closed_orders(self.wallet.address) order = list(filter(lambda order: order.transaction_hash == hash, api_response.order)) if order: binance_logger.debug(f'order detail {order[0]}') binance_logger.info(f'order status {order[0].status}') return order[0] time.sleep(0.5) except ApiException as e: binance_logger.debug(f'Exception when calling get_closed_orders() {e}') def dex_buy(self, symbol, quantity, price=None, bnb=True): self.wallet.reload_account_sequence() if price is None: depth = self.depth(symbol, bnb=bnb) price = float(depth.asks[0][0]) if bnb: pair = symbol + '_' + self.BNB else: pair = symbol + '_' + self.BUSD buy_msg = NewOrderMsg( wallet=self.wallet, symbol=pair, time_in_force=TimeInForce.IMMEDIATE_OR_CANCEL, order_type=OrderType.LIMIT, side=OrderSide.BUY, price=price, quantity=quantity ) res = self.client.broadcast_msg(buy_msg, sync=True) binance_logger.info(f'buy response: {res}') hash = res[0]['hash'] time.sleep(0.1) return self.binance_check_hash(hash) def dex_sell(self, symbol, quantity, price=None, bnb=True): self.wallet.reload_account_sequence() if price is None: depth = self.depth(symbol, bnb=bnb) price = float(depth.bids[0][0]) if bnb: pair = symbol + '_' + self.BNB else: pair = symbol + '_' + self.BUSD sell_msg = NewOrderMsg( wallet=self.wallet, symbol=pair, time_in_force=TimeInForce.IMMEDIATE_OR_CANCEL, order_type=OrderType.LIMIT, side=OrderSide.SELL, price=price, quantity=quantity ) res = self.client.broadcast_msg(sell_msg, sync=True) binance_logger.info(f'sell response: {res}') hash = res[0]['hash'] time.sleep(0.1) return self.binance_check_hash(hash) def thor_swap(self, chain, i_symbol, o_symbol, amount, to_address, dest_address=None, limit=None): if limit: memo = 'SWAP:' + chain + '.' + o_symbol + '::' + str(int(limit * 10**8)) else: memo = 'SWAP:' + chain + '.' + o_symbol transfer_msg = TransferMsg( wallet=self.wallet, symbol=i_symbol, amount=amount, to_address=to_address, memo=memo ) res = self.client.broadcast_msg(transfer_msg, sync=True) binance_logger.info(f'swap response: {res}') hash = res[0]['hash'] return hash def thor_smart_swap(self, chain, i_symbol, o_symbol, amount, to_address, dest_address=None, limit=None, slice=3): hashes = [] piece = float(amount/slice) binance_logger.info(f'smart swap with {slice} slices of {piece} {i_symbol}') if limit: piece_limit = float(limit/slice) for part in range(slice): hash = self.thor_swap(chain=chain, i_symbol=i_symbol, o_symbol=o_symbol, amount=piece, to_address=to_address, limit=piece_limit) hashes.append(hash) else: for part in range(slice): hash = self.thor_swap(chain=chain, i_symbol=i_symbol, o_symbol=o_symbol, amount=piece, to_address=to_address) hashes.append(hash) return hashes def thor_stake(self, chain, symbol, amount, runeamount, to_address): memo = 'STAKE:' + chain + '.' + symbol multi_transfer_msg = MultiTransferMsg( wallet=self.wallet, transfers=[ Transfer(symbol=self.RUNE, amount=runeamount), Transfer(symbol=symbol, amount=amount), ], to_address=to_address, memo=memo ) res = self.client.broadcast_msg(multi_transfer_msg, sync=True) binance_logger.info(f'stake response: {res}') hash = res[0]['hash'] return hash def thor_withdraw(self, chain, symbol, percent, to_address): if chain == 'BNB': payload = 0.00000001 payload_token = 'BNB' else: payload = 0 percent_str = str(int(percent * 10**2)) memo = "WITHDRAW:" + chain + '.' + symbol + ':' + percent_str transfer_msg = TransferMsg( wallet=self.wallet, symbol=payload_token, amount=payload, to_address=to_address, memo=memo ) res = self.client.broadcast_msg(transfer_msg, sync=True) binance_logger.info(f'withdraw response: {res}') hash = res[0]['hash'] return hash
class Monitor(object): def __init__(self): self.env = BinanceEnvironment( api_url='https://dex-asiapacific.binance.org', wss_url='wss://dex.binance.org/api/', hrp='bnb') with open('bnb_real_key.txt', 'r') as f: key = f.readline() # for testing # self.env = BinanceEnvironment.get_testnet_env() # with open('bnb_key.txt', 'r') as f: # key = f.readline() f.close() self.client = HttpApiClient(env=self.env) self.wallet = Wallet(key, env=self.env) self.address = self.wallet.address self.ftx = ccxt.ftx({ 'apiKey': '6p37l5AXOzIgFzfeSzkcuPhuaYcw3GcpJrU83ROy', 'secret': '3SphoJJ6Gl_w5pPPkGmQpKbVGN9oPiUgxqs4ob_H' }) #self.assets = ['USD', 'USDT'] self.assets = ['USD'] self.BNB_BUSD = 'BNB.BUSD-BD1' self.pool = THORChain() def get_bnb_balance(self, asset=None): balance = self.client.get_account(self.address)['balances'] if asset: return next( filter(lambda symbol: symbol['symbol'] == asset, balance))['free'] return balance def thor_swap(self, i_symbol, amount, to_address, memo): self.wallet.reload_account_sequence() transfer_msg = TransferMsg(wallet=self.wallet, symbol=i_symbol, amount=amount, to_address=to_address, memo=memo) res = self.client.broadcast_msg(transfer_msg, sync=True) tx_hash = res[0]['hash'] arb_logger.debug(f'broadcasting {tx_hash}') return tx_hash def round_down(self, number, precision): return decimal_to_precision(number_to_string(number), TRUNCATE, precision) def book_oracle_asks(self, book, level, max_output, omega=0.8): """ return array of available volume and corresponding unit price """ book_output_volume = [] book_output_price = [] cap = False # Book[0-level][0] = Price; Book[0-level][1] = Volume for i in range(0, level): if cap: break book_output_price.append(book[i][0]) out_volume = book[i][1] * omega if i > 0: out_volume += book_output_volume[i - 1] if out_volume >= max_output: book_output_volume.append(max_output) cap = True else: book_output_volume.append(out_volume) return book_output_price, book_output_volume def ftx_price_feed_buy(self, asset, level=7, max_rune=700): """Buy Rune on Ftx, sell Rune on Bepswap""" ftx_balance = self.get_ftx_balance() if ftx_balance['USD'] < 720: arb_logger.info(f'need recharge') self.deposit_ftx() while True: time.sleep(1) ftx_balance = self.get_ftx_balance() if ftx_balance['USD'] > 720: arb_logger.info(f'recharge finished') break while True: try: book = self.ftx.fetch_order_book(f'RUNE/{asset}', level) break except RequestTimeout as e: arb_logger.debug( 'Request timeout calling self.ftx.fetch_order_book: {e}') arb_logger.debug(f'route 2: fetching asset {asset} \n {book}') # Route 2: clearing ask side oracle = self.book_oracle_asks(book['asks'], level, max_rune) fee = 1.0007 oracle_out_price = oracle[0] oracle_out_volume = oracle[1] for i in range(0, len(oracle_out_volume)): out_price = oracle_out_price[i] out_volume_order = self.round_down(oracle_out_volume[i], 1) in_volume = out_price * float(out_volume_order) out_volume_real = oracle_out_volume[i] / fee # book: asset -> rune arb_logger.debug( f'book: {in_volume} {asset} := {out_volume_real} RUNE ' f'RUNE market ask price := {out_price}') # pool: rune -> asset route = self.pool.get_swap_memo(self.BNB_BUSD, 'sell', amount=out_volume_real, limit=in_volume) expected = route[0] optimal_slice = route[1] optimal_expected = route[2] pool_address = route[3] memo = route[4] arb_logger.debug( f'pool: {out_volume_real} RUNE := {expected} {asset}') arb_logger.debug(f'{memo}') diff = expected - in_volume if diff > 1.3: arb_logger.error(f'profit: {diff}') ftx_order = self.ftx.create_order(symbol=f'RUNE/{asset}', side='buy', amount=out_volume_order, price=out_price, type='limit') tx_hash = self.thor_swap(self.pool.rune, float(out_volume_real), pool_address, memo) self.pool.get_tx_in(tx_hash=tx_hash) database.insert_tx({ 'thorchain': tx_hash, 'ftx': ftx_order['id'] }) break else: arb_logger.warning(f'profit: {diff}') time.sleep(0.2) def get_ftx_balance(self): try: return self.ftx.fetch_balance()['total'] except Exception as e: print(e) def get_ftx_deposit(self, coin='RUNE'): try: return self.ftx.fetch_deposit_address(coin) except Exception as e: print(e) def deposit_ftx(self): info = self.get_ftx_deposit() address = info['address'] memo = info['tag'] balance = self.get_bnb_balance(asset='BUSD-BD1') print(f'sending {balance} to {address} with memo {memo}') self.thor_swap('BUSD-BD1', float(balance) - 1, address, memo) def profit_report(self): database.add_timestamp() txs = database.get_unaccounted_txs() rune_gain = 0 usd_gain = 0 for tx in txs: thor_order = self.pool.get_tx_out(tx_hash=tx['thorchain']) thor_rune_in = int(thor_order._in.coins[0]['amount']) / 10**8 thor_usd_out = int(thor_order.out[0].coins[0]['amount']) / 10**8 ftx_order = self.ftx.fetch_order(tx['ftx']) ftx_usd_in = ftx_order['cost'] ftx_rune_out = ftx_order['filled'] ftx_remaining = ftx_order['remaining'] if ftx_remaining != 0: database.add_profit_txs(tx['_id'], 0, 0, 'ftx fail') elif thor_order.out[0].coins[0]['asset'] == 'BNB.RUNE-B1A': database.add_profit_txs(tx['_id'], -1 * ftx_usd_in, ftx_rune_out - 1, 'thor fail') else: temp_rune_gain = ftx_rune_out - thor_rune_in temp_usd_gain = thor_usd_out - ftx_usd_in database.add_profit_txs(tx['_id'], temp_usd_gain, temp_rune_gain, 'success') rune_gain += temp_rune_gain usd_gain += temp_usd_gain print(f'rune_gain: {rune_gain}') print(f'usd_gain: {usd_gain}') def fail_report(self): txs = database.get_failed_txs() for tx in txs: print(tx)