├── lib ├── .gitignore ├── graviex_api.py ├── logo.txt ├── binance_api.py ├── rpclib.py ├── coinslib.py └── tuilib.py ├── scripts ├── .gitignore └── parse_logs.py ├── update_coins.sh ├── MM2_example.json ├── api_keys_example.json ├── .gitignore ├── stats └── get_mm2_stats.py ├── get_orderbook.py ├── mm2_tui.py ├── README.md └── LICENSE /lib/.gitignore: -------------------------------------------------------------------------------- 1 | coinslib.py -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | logs\ 2 | -------------------------------------------------------------------------------- /update_coins.sh: -------------------------------------------------------------------------------- 1 | rm coins 2 | wget https://raw.githubusercontent.com/KomodoPlatform/coins/master/coins 3 | -------------------------------------------------------------------------------- /MM2_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "gui":"pytomicDEX", 3 | "netid":9999, 4 | "rpc_password":"ENTER SECURE RPC PASSWORD", 5 | "passphrase":"ENTER A SECURE PASSPHRASE", 6 | "userhome":"/home/YOURUSERNAME/" 7 | } 8 | -------------------------------------------------------------------------------- /api_keys_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "binance_key":"", 3 | "binance_secret":"", 4 | "graviex_key":"", 5 | "graviex_secret":"", 6 | "coinspot_key":"", 7 | "coinspot_secret":"" 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | snipe.py 2 | *.zip 3 | update_mm2.sh 4 | __pycache__ 5 | mm2_output.log 6 | mm2.log 7 | coins 8 | bot.log 9 | DB 10 | mm2 11 | logs/ 12 | stats/ 13 | api_keys.json 14 | MM2.json 15 | run_bot.py 16 | mm2coins.py 17 | venv/ 18 | -------------------------------------------------------------------------------- /scripts/parse_logs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | 6 | # Change this to your log filename 7 | try: 8 | logfilename = sys.argv[1] 9 | except: 10 | print("use like: parse_logs.py logfile.log") 11 | 12 | if not os.path.isdir("MAKER"): 13 | os.makedirs("MAKER") 14 | if not os.path.isdir("TAKER"): 15 | os.makedirs("TAKER") 16 | 17 | with open(logfilename, "r") as f: 18 | lines = f.readlines() 19 | for line in lines: 20 | if line.find('getRecentSwaps') > -1: 21 | print(line) 22 | swap_json = line.split('getRecentSwaps')[1] 23 | try: 24 | swap_results = json.loads(swap_json)['result']['swaps'] 25 | for swap in swap_results: 26 | if swap['type'] == 'Taker': 27 | folder = "TAKER" 28 | elif swap['type'] == 'Maker': 29 | folder = "MAKER" 30 | uuid = swap['uuid'] 31 | with open(folder+"/"+uuid+".json", "w") as j: 32 | print("writing "+folder+"/"+uuid+".json") 33 | j.write(json.dumps(swap)) 34 | except json.decoder.JSONDecodeError: 35 | pass 36 | -------------------------------------------------------------------------------- /lib/graviex_api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import hashlib 3 | import hmac 4 | import urllib2 5 | import urllib 6 | import time 7 | import ssl 8 | 9 | try: 10 | with open(sys.path[0]+"/api_keys.json") as keys_j: 11 | keys_json = json.load(keys_j) 12 | except FileNotFoundError: 13 | print("You need api_keys.json file in PytomicDEX directory") 14 | print("Check api_keys_example.json, create file and run me again") 15 | exit() 16 | 17 | access_key = keys_json['graviex_key'] 18 | secret_key = keys_json['graviex_secret'] 19 | base_url = 'https://graviex.net/api/v2' 20 | 21 | # https://graviex.net/documents/api_v2 22 | # https://graviex.net/api/v2/order_book.json?market=labskmd 23 | # https://graviex.net/api/v2/order_book.json?market=labsbtc 24 | # https://graviex.net/api/v2/deposit_address.json 25 | 26 | get_endpoints = [] 27 | post_endpoints = [] 28 | 29 | # 0. making ssl context - verify should be turned off 30 | ctx = ssl.create_default_context() 31 | ctx.check_hostname = False 32 | ctx.verify_mode = ssl.CERT_NONE 33 | 34 | def build_query(method, endpoint, params=''): 35 | epoch_time = str(int(time.time()))+'000' 36 | request = 'access_key='+access_key+'&tonce='+epoch_time+params 37 | message = method+'|'+endpoint+'|'+request 38 | signature = hmac.new(secret_key,message,hashlib.sha256).hexdigest() 39 | if method == 'GET': 40 | query = base_url+"/"+endpoint+'?'+request+'&signature='+signature 41 | content = urllib2.urlopen(query, context=ctx).read() 42 | elif method == 'POST': 43 | query = base_url+"/"+endpoint+'?'+request 44 | result = urllib2.Request(query, urllib.urlencode({'signature' : signature})) 45 | content = urllib2.urlopen(result, context=ctx).read() 46 | print(content) 47 | -------------------------------------------------------------------------------- /lib/logo.txt: -------------------------------------------------------------------------------- 1 | ............................... ´~»*&o££±£Xo&*»;^´ .............................. 2 | ......................... ~*±ëëëëëëëëëëëë±±££Xoo%&&Ií ........................... 3 | ..................... /£ëëëëëëëëëëëëëëëëë±±££Xo%%&IIIí .......................... 4 | ................. ,&ëëëëëëëëëëëëëëëëëëëëë±±££Xo%%&II*~ .......................... 5 | ............... =ëëëëëëëëëëëëëëëëëëë±X%&IIII%oo%%&I*' ............ ´´ ............. 6 | ............ ,XëëëëëëëëëëëëëëX», ............................. í*******?´ ......... 7 | .......... ,£ëëëëëëëëëëëë%^ ................................ ´***********~ ........ 8 | ......... %ëëëëëëëëëëë&, ................................... /************ ........ 9 | ....... íëëëëëëëëëë±í ...................................... ?***********= ........ 10 | ...... &ëëëëëëëëë±~ ....................................... ^************, ........ 11 | ..... Xëëëëëëëëë» ........................................ '**********=~ .......... 12 | ... ´£ëëëëëëëë£, ........................................ »****/' ................. 13 | ... Xëëëëëëëë% ....................................... ^=***/ ..................... 14 | .. Iëëëëëëëë& ..................... ~*o%%%%%&&&&I==*I*****?´ ...................... 15 | . íëëëëëëëëo ................... íoXXoooo%%%%%&&&&IIIII**» ................ '*IIII; . 16 | . Xëëëëëëëë, ................. ;££XXXXXoooo%%%%%&&&&IIIII´ ............... íII&&&&&= 17 | ~ëëëëëëëë= ................. &±££££XXXXXoooo%%%%%&&&&II= ................ /%%%%%%%% 18 | *ëëëëëëëë, ................ &±±±±££££XXXXXoooo%%%%%&&&&* ................ ,oooooooX 19 | oëëëëëëëX ................ /ëë±±±±±££££XXXXXoooo%%%%%&&&~ ................ %£££££££ 20 | ±ëëëëëëëI ................ %ëëëë±±±±±££££XXXXXoooo%%%%%&? ................ I±±±±±±± 21 | ±ëëëëëëëI ................ X@ëëëëë±±±±±££££XXXXXoooo%%%%= ................ Iëëëëëëë 22 | £ëëëëëëë% ................ I@@@@ëëëë±±±±±££££XXXXXoooo%%/ ................ %ëëëëëëë 23 | &ëëëëëëë± ................ '@@@@@@ëëëë±±±±±££££XXXXXooo%´ ................ ëëëëëëëë 24 | »ëëëëëëëëí ................ ;@@@@@@@ëëëë±±±±±££££XXXXXo^ ................ ;ëëëëëëëë 25 | ´ëëëëëëëëo ................. ^ë@@@@@@ëëëëë±±±±±££££XXo, ................. Xëëëëëëëë 26 | . *ëëëëëëëë» .................. »@@@@@@@@ëëëë±±±±±£££; .................. »ëëëëëëëë= 27 | . ´±ëëëëëëëë~ ................... ^o@@@@@@@ëëëë±±±&' ................... ~ëëëëëëëë± . 28 | .. ~ëëëëëëëëë^ ...................... ,/*%oo&=;, ...................... ^ëëëëëëëëë^ . 29 | ... /ëëëëëëëëë; .................................................... ;ëëëëëëëëë/ .. 30 | .... »ëëëëëëëëë& .................................................. &ëëëëëëëëë/ ... 31 | ..... íëëëëëëëëëëí .............................................. íëëëëëëëëëëí .... 32 | ...... ,±ëëëëëëëëë±~ .......................................... ~±ëëëëëëëëë£, ..... 33 | ........ =ëëëëëëëëëëë» ...................................... ?ëëëëëëëëëëë= ....... 34 | ......... ´Xëëëëëëëëëëë£; ................................ ;±ëëëëëëëëëëëo´ ........ 35 | ........... '£ëëëëëëëëëëëëë&~ ........................ ~&ëëëëëëëëëëëëëX, .......... 36 | ............. ´%ëëëëëëëëëëëëëëëëo=í' .......... ';=oëëëëëëëëëëëëëëëë&´ ............ 37 | ................ í±ëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëë£í ............... 38 | ................... íXëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëXí .................. 39 | ...................... ´/XëëëëëëëëëëëëëëëëëëëëëëëëëëëëëëX/´ ..................... 40 | ........................... ´í=Xëëëëëëëëëëëëëëëëëëo=~ ........................... 41 | ............................... ´~»*&o££±£Xo&*»;^´ .............................. -------------------------------------------------------------------------------- /stats/get_mm2_stats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | from os.path import expanduser 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'lib')) 7 | try: 8 | from coinslib import coins, trading_list 9 | except: 10 | print("coinslib not found! copy mm2coins_example.py and modify as required.") 11 | sys.exit(0) 12 | 13 | home = expanduser("~") 14 | 15 | error_events = [ 16 | "StartFailed", 17 | "NegotiateFailed", 18 | "TakerFeeValidateFailed", 19 | "MakerPaymentTransactionFailed", 20 | "MakerPaymentDataSendFailed", 21 | "TakerPaymentValidateFailed", 22 | "TakerPaymentSpendFailed", 23 | "MakerPaymentRefunded", 24 | "MakerPaymentRefundFailed" 25 | ] 26 | 27 | # assuming start from DB/%NODE_PUBKEY%/SWAPS/STATS/ directory 28 | def fetch_local_swap_files(node_pubkey): 29 | print(home+"/pytomicDEX/DB/"+node_pubkey+"/SWAPS/STATS/") 30 | os.chdir(home+"/pytomicDEX/DB/"+node_pubkey+"/SWAPS/STATS/") 31 | files_list_tmp = os.listdir("MAKER") 32 | files_list = [] 33 | for file in files_list_tmp: 34 | if file[-5:] == '.json': 35 | files_list.append(file) 36 | 37 | files_content = {} 38 | 39 | # loading files content into files_content dict 40 | for file in files_list: 41 | try: 42 | with open('MAKER/'+file) as json_file: 43 | swap_uuid = file[:-5] 44 | data = json.load(json_file) 45 | files_content[swap_uuid] = data 46 | except Exception as e: 47 | print(e) 48 | print("Broken: " + file) 49 | return files_content 50 | 51 | # filter swaps data for speciifc pair 52 | def pair_filter(data_to_filter, maker_coin, taker_coin): 53 | swaps_of_pair = {} 54 | for swap_data in data_to_filter.values(): 55 | try: 56 | if swap_data["events"][0]["event"]["data"]["taker_coin"] == taker_coin and swap_data["events"][0]["event"]["data"]["maker_coin"] == maker_coin: 57 | swaps_of_pair[swap_data["events"][0]["event"]["data"]["uuid"]] = swap_data 58 | except Exception: 59 | pass 60 | return swaps_of_pair 61 | 62 | # filter for time period 63 | def time_filter(data_to_filter, start_time_stamp, end_time_stamp): 64 | swaps_for_dates = {} 65 | for swap_data in data_to_filter.values(): 66 | try: 67 | if swap_data["events"][0]["timestamp"] >= start_time_stamp and swap_data["events"][0]["timestamp"] <= end_time_stamp: 68 | swaps_for_dates[swap_data["events"][0]["event"]["data"]["uuid"]] = swap_data 69 | except Exception as e: 70 | pass 71 | return swaps_for_dates 72 | 73 | # checking if swap succesfull 74 | def count_successful_swaps(swaps_data): 75 | fails_info = [] 76 | error_type_counts = {} 77 | for event in error_events: 78 | error_type_counts[event] = 0 79 | successful_swaps_counter = 0 80 | failed_swaps_counter = 0 81 | i = 0 82 | for swap_data in swaps_data.values(): 83 | failed = False 84 | for event in swap_data["events"]: 85 | if event["event"]["type"] in error_events: 86 | error_type_counts[event["event"]["type"]] += 1 87 | taker_coin = swap_data["events"][0]['event']['data']['taker_coin'] 88 | maker_coin = swap_data["events"][0]['event']['data']['maker_coin'] 89 | taker_pub = swap_data["events"][0]['event']['data']['taker'] 90 | fail_uuid = swap_data['uuid'] 91 | fail_timestamp = event['timestamp'] 92 | fail_error = event["event"]["data"]['error'] 93 | fail_data = { 94 | "uuid": fail_uuid, 95 | "time": fail_timestamp, 96 | "pair": taker_coin+" - "+maker_coin, 97 | "taker_pub": taker_pub, 98 | "error": fail_error 99 | } 100 | fails_info.append(fail_data) 101 | failed = True 102 | break 103 | if failed: 104 | failed_swaps_counter += 1 105 | else: 106 | successful_swaps_counter += 1 107 | return (failed_swaps_counter, successful_swaps_counter, error_type_counts, fails_info) 108 | 109 | # calculate volumes, assumes filtered data for pair 110 | def calculate_trades_volumes(swaps_data): 111 | maker_coin_volume = 0 112 | taker_coin_volume = 0 113 | for swap_data in swaps_data.values(): 114 | try: 115 | maker_coin_volume += float(swap_data["events"][0]["event"]["data"]["maker_amount"]) 116 | taker_coin_volume += float(swap_data["events"][0]["event"]["data"]["taker_amount"]) 117 | except Exception as e: 118 | print(swap_data["events"][0]) 119 | print(e) 120 | return (maker_coin_volume, taker_coin_volume) 121 | 122 | node_pubkeys = os.listdir(home+"/pytomicDEX/DB") 123 | for node_pubkey in node_pubkeys: 124 | print(node_pubkey) 125 | swap_data = fetch_local_swap_files(node_pubkey) 126 | for maker in coins: 127 | for taker in coins: 128 | if maker != taker: 129 | swap_count = pair_filter(swap_data, maker, taker) 130 | if len(swap_count) > 0: 131 | print(maker+"-"+taker+": "+str(len(swap_count))) 132 | 133 | swaps = count_successful_swaps(swap_data) 134 | print("Total successful: "+str(swaps[1])) 135 | print("Total failed: "+str(swaps[0])) 136 | print("Error Type Counts: "+str(swaps[2])) 137 | print("Fail info: "+str(swaps[3])) 138 | 139 | volumes = calculate_trades_volumes(swap_data) 140 | print("Total maker volume: "+str(volumes[0])) 141 | print("Total taker volume: "+str(volumes[1])) 142 | -------------------------------------------------------------------------------- /get_orderbook.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | import time 6 | import requests 7 | import subprocess 8 | from subprocess import Popen 9 | from os.path import expanduser 10 | from lib import coinslib 11 | 12 | # Get and set config 13 | cwd = os.getcwd() 14 | home = expanduser("~") 15 | mm2_path= home+"/pytomicDEX" 16 | mm2log_path= home+"/pytomicDEX/logs" 17 | 18 | orderbook_json = '/var/www/html/json/mm2_orderbook.json' 19 | orderbook_json2 = '/var/www/html/json/mm2_orderbook2.json' 20 | 21 | def get_modified_time(file): 22 | #return time.ctime(os.path.getmtime(file)) 23 | return os.path.getmtime(file) 24 | 25 | with open(mm2_path+"/MM2.json") as j: 26 | mm2json = json.load(j) 27 | 28 | gui = mm2json['gui'] 29 | netid = mm2json['netid'] 30 | passphrase = mm2json['passphrase'] 31 | user_pass = mm2json['rpc_password'] 32 | rpc_password = mm2json['rpc_password'] 33 | node_ip = "http://127.0.0.1:7783" 34 | 35 | def help_mm2(node_ip, user_pass): 36 | params = {'userpass': user_pass, 'method': 'help'} 37 | r = requests.post(node_ip, json=params) 38 | return r.text 39 | 40 | def check_mm2_status(node_ip, user_pass): 41 | try: 42 | help_mm2(node_ip, user_pass) 43 | return True 44 | except Exception as e: 45 | return False 46 | 47 | def start_mm2(logfile): 48 | mm2_output = open(logfile,'w+') 49 | os.chdir(mm2_path) 50 | Popen(["./mm2"], stdout=mm2_output, stderr=mm2_output, universal_newlines=True) 51 | print("Marketmaker 2 started. Use 'tail -f "+logfile+"' for mm2 console messages") 52 | os.chdir(cwd) 53 | 54 | 55 | def activate_all(node_ip, user_pass): 56 | for coin in coinslib.coins: 57 | if coinslib.coins[coin]['activate_with'] == 'native': 58 | r = enable(node_ip, user_pass, coin) 59 | else: 60 | r = electrum(node_ip, user_pass, coin) 61 | 62 | 63 | def enable(node_ip, user_pass, cointag, tx_history=True): 64 | coin = coinslib.coins[cointag] 65 | params = {'userpass': user_pass, 66 | 'method': 'enable', 67 | 'coin': cointag, 68 | 'mm2':1, 69 | 'tx_history':tx_history,} 70 | r = requests.post(node_ip, json=params) 71 | return r 72 | 73 | def electrum(node_ip, user_pass, cointag, tx_history=True): 74 | coin = coinslib.coins[cointag] 75 | if 'contract' in coin: 76 | params = {'userpass': user_pass, 77 | 'method': 'enable', 78 | 'urls':coin['electrum'], 79 | 'coin': cointag, 80 | 'swap_contract_address': coin['contract'], 81 | 'mm2':1, 82 | 'tx_history':tx_history,} 83 | else: 84 | params = {'userpass': user_pass, 85 | 'method': 'electrum', 86 | 'servers':coin['electrum'], 87 | 'coin': cointag, 88 | 'mm2':1, 89 | 'tx_history':tx_history,} 90 | r = requests.post(node_ip, json=params) 91 | return r 92 | 93 | def orderbook(node_ip, user_pass, base, rel): 94 | params = {'userpass': user_pass, 95 | 'method': 'orderbook', 96 | 'base': base, 'rel': rel,} 97 | r = requests.post(node_ip, json=params) 98 | return r 99 | 100 | def get_orders_json(node_ip, user_pass, coins): 101 | orders = [] 102 | ask_json = [] 103 | bid_json = [] 104 | for base in coins: 105 | for rel in coins: 106 | if base != rel: 107 | orderbook_response = orderbook(node_ip, user_pass, base, rel).json() 108 | orders.append(orderbook_response) 109 | print(orderbook_response) 110 | for pair in orders: 111 | if 'asks' in pair: 112 | if len(pair['asks']) > 0: 113 | baserel = pair['rel']+"/"+pair['base'] 114 | print(baserel) 115 | for ask in pair['asks']: 116 | print(str(ask['price'])[:12]+" "+pair['rel']+" per "+pair['base']+" ("+str(ask['maxvolume'])+" "+pair['base']+" available)") 117 | baserel = pair['base']+"/"+pair['rel'] 118 | ask_json.append({"pair":baserel, "price":str(str(ask['price'])[:12]+" "+pair['rel']), "volume":str(str(ask['maxvolume'])+" "+pair['base'])}) 119 | # for bid in pair['bids']: 120 | # print(str(bid['price'])+" "+pair['base']+" per "+pair['rel']+" ("+str(bid['maxvolume'])+" available)") 121 | # bid_json.append({"baserel":baserel, "price":bid['price'], "volume":str(bid['maxvolume'])}) 122 | return ask_json, bid_json 123 | 124 | def stop_mm2(node_ip, user_pass): 125 | params = {'userpass': user_pass, 'method': 'stop'} 126 | try: 127 | r = requests.post(node_ip, json=params) 128 | msg = "MM2 stopped. " 129 | except: 130 | msg = "MM2 was not running. " 131 | 132 | mm2_running = check_mm2_status(node_ip, user_pass) 133 | 134 | if mm2_running: 135 | print("mm2 is running") 136 | orderbook = get_orders_json(node_ip, user_pass, coinslib.coins) 137 | table_data = orderbook[0]+orderbook[1] 138 | activate_all(node_ip, user_pass) 139 | else: 140 | start_mm2('mm2.log') 141 | time.sleep(10) 142 | activate_all(node_ip, user_pass) 143 | orderbook = get_orders_json(node_ip, user_pass, coinslib.coins) 144 | table_data = orderbook[0]+orderbook[1] 145 | pass 146 | if table_data == []: 147 | stop_mm2(node_ip, user_pass) 148 | time.sleep(15) 149 | start_mm2('mm2.log') 150 | time.sleep(10) 151 | activate_all(node_ip, user_pass) 152 | orderbook = get_orders_json(node_ip, user_pass, coinslib.coins) 153 | table_data = orderbook[0]+orderbook[1] 154 | 155 | table_json = str(table_data).replace("'",'"') 156 | print(table_json) 157 | jsonfile = orderbook_json 158 | if os.path.isfile(orderbook_json2): 159 | pass 160 | else: 161 | jsonfile = orderbook_json2 162 | try: 163 | if os.path.isfile(orderbook_json2): 164 | print(get_modified_time(orderbook_json)) 165 | print(get_modified_time(orderbook_json2)) 166 | if get_modified_time(orderbook_json) > get_modified_time(orderbook_json2): 167 | jsonfile = orderbook_json2 168 | except Exception as e: 169 | jsonfile = orderbook_json 170 | print(e) 171 | pass 172 | print(jsonfile) 173 | with open(jsonfile, "w+") as f: 174 | f.write(str(table_json)) 175 | -------------------------------------------------------------------------------- /mm2_tui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | import time 6 | import requests 7 | import subprocess 8 | from os.path import expanduser 9 | from lib import rpclib, tuilib, coinslib, binance_api 10 | 11 | # Get and set config 12 | cwd = os.getcwd() 13 | script_path = sys.path[0] 14 | home = expanduser("~") 15 | 16 | 17 | header = "\ 18 | _ _ _____ ________ __ \n\ 19 | /\ | | (_) | __ \| ____\ \ / / \n\ 20 | / \ | |_ ___ _ __ ___ _ ___| | | | |__ \ V / \n\ 21 | / /\ \| __/ _ \| '_ ` _ \| |/ __| | | | __| > < \n\ 22 | / ____ \ || (_) | | | | | | | (__| |__| | |____ / . \ \n\ 23 | /_/ \_\__\___/|_| |_| |_|_|\___|_____/|______/_/ \_\ \n\ 24 | \n" 25 | 26 | 27 | author = '{:^65}'.format('Welcome to the AtomicDEX TUI v0.2 by Thorn Mennet') 28 | 29 | no_params_list = ["Start MarketMaker 2"] 30 | 31 | def main(): 32 | try: 33 | with open(script_path+"/MM2.json") as j: 34 | mm2json = json.load(j) 35 | gui = mm2json['gui'] 36 | netid = mm2json['netid'] 37 | passphrase = mm2json['passphrase'] 38 | userpass = mm2json['rpc_password'] 39 | rpc_password = mm2json['rpc_password'] 40 | local_ip = "http://127.0.0.1:7783" 41 | MM2_json_exists = True 42 | except: 43 | MM2_json_exists = False 44 | pass 45 | while True: 46 | os.system('clear') 47 | print(tuilib.colorize(header, 'lightgreen')) 48 | print(tuilib.colorize(author, 'cyan')) 49 | menuItems = [] 50 | if not MM2_json_exists: 51 | print(tuilib.colorize("No MM2.json file!", 'red')) 52 | menuItems.append({"Setup MM2.json file": tuilib.create_MM2_json}) 53 | else: 54 | status = rpclib.get_status(local_ip, userpass) 55 | print('{:^84}'.format(status[0])) 56 | num_orders = status[4] 57 | if status[1]: 58 | swaps_info = tuilib.swaps_info(local_ip, userpass) 59 | num_swaps = swaps_info[1] 60 | num_finished = swaps_info[2] 61 | num_failed = swaps_info[3] 62 | num_in_progress = swaps_info[4] 63 | print(tuilib.colorize('{:^68}'.format("[Total swaps: "+str(num_swaps)+"] [Failed swaps: "+str(num_failed)+"] [In Progress: "+str(num_in_progress)+"] "), 'orange')) 64 | print(tuilib.colorize('{:^68}'.format("[You have "+str(num_orders)+" orders active in the orderbook]"), 'orange')) 65 | if len(sys.argv) > 1: 66 | if sys.argv[1] == 'runbot': 67 | tuilib.run_tradebot(local_ip, userpass) 68 | # Build Menu 69 | if status[1] is False: 70 | menuItems.append({"Start MarketMaker 2": tuilib.start_mm2}) 71 | else: 72 | menuItems.append({"Stop MarketMaker 2": tuilib.stop_mm2}) 73 | if status[2] is False: 74 | menuItems.append({"Activate coins": tuilib.activate_all}) 75 | if len(status[3]) > 0: 76 | menuItems.append({"View/withdraw balances": tuilib.show_balances_table}) 77 | menuItems.append({"View/buy from orderbook": tuilib.show_orderbook_pair}) 78 | menuItems.append({"View/cancel my orders": tuilib.show_orders_table}) 79 | menuItems.append({"View swaps in progress": tuilib.show_pending_swaps}) 80 | menuItems.append({"Review recent swaps": tuilib.show_recent_swaps}) 81 | menuItems.append({"Review failed swaps": tuilib.show_failed_swaps}) 82 | menuItems.append({"Recover stuck swap": tuilib.recover_swap}) 83 | if binance_api.api_key != '': 84 | menuItems.append({"Run Tradebot": tuilib.run_tradebot}) 85 | if binance_api.api_key != '': 86 | menuItems.append({"View Binance Account Info (needs valid API keys)": tuilib.binance_account_info}) 87 | menuItems.append({"Exit TUI": tuilib.exit}) 88 | print("\n") 89 | for item in menuItems: 90 | print(tuilib.colorize("[" + str(menuItems.index(item)) + "] ", 'blue') + tuilib.colorize(list(item.keys())[0],'blue')) 91 | choice = input(tuilib.colorize("Select menu option: ", 'orange')) 92 | try: 93 | if int(choice) < 0: 94 | raise ValueError 95 | # Call the matching function 96 | if list(menuItems[int(choice)].keys())[0] == "Setup MM2.json file": 97 | list(menuItems[int(choice)].values())[0]() 98 | try: 99 | with open(script_path+"/MM2.json") as j: 100 | mm2json = json.load(j) 101 | gui = mm2json['gui'] 102 | netid = mm2json['netid'] 103 | passphrase = mm2json['passphrase'] 104 | userpass = mm2json['rpc_password'] 105 | rpc_password = mm2json['rpc_password'] 106 | local_ip = "http://127.0.0.1:7783" 107 | MM2_json_exists = True 108 | except: 109 | input(colorize("Error in MM2.json file! See MM2_example.json for a valid example...")) 110 | MM2_json_exists = False 111 | pass 112 | elif list(menuItems[int(choice)].keys())[0] in no_params_list: 113 | list(menuItems[int(choice)].values())[0]() 114 | elif list(menuItems[int(choice)].keys())[0].find('Menu') != -1: 115 | submenu(list(menuItems[int(choice)].values())[0]) 116 | else: 117 | list(menuItems[int(choice)].values())[0](local_ip, userpass) 118 | except (ValueError, IndexError): 119 | pass 120 | 121 | if __name__ == "__main__": 122 | while True: 123 | os.system('clear') 124 | print("\n\n") 125 | with (open("lib/logo.txt", "r")) as logo: 126 | for line in logo: 127 | parts = line.split(' ') 128 | row = '' 129 | for part in parts: 130 | if part.find('.') == -1: 131 | row += tuilib.colorize(part, 'blue') 132 | else: 133 | row += tuilib.colorize(part, 'black') 134 | print(row, end='') 135 | #print(line, end='') 136 | time.sleep(0.04) 137 | time.sleep(0.4) 138 | print("\n") 139 | break 140 | main() 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pytomicDEX 2 | 3 | **Superceeded by https://github.com/smk762/pytomicDEX_makerbot** 4 | 5 | ### A Python TUI for using [Komodo Platform](https://komodoplatform.com/)'s [AtomicDEX](https://atomicdex.io/) from the command line 6 | *Note: Only tested in Ubuntu 18.04 so far.* 7 | 8 | ![pytomicDEX TUI](https://i.imgur.com/eFjW83f.png) 9 | 10 | #### Requirements 11 | Python 3.6+ 12 | 13 | #### Dependancies 14 | ``` 15 | pip3 install requests json subprocess python-bitcoinlib 16 | ``` 17 | #### Recommended reading 18 | https://developers.atomicdex.io/ 19 | https://developers.komodoplatform.com/ 20 | https://komodoplatform.com/atomic-swaps/ 21 | 22 | ## Setup 23 | 24 | ### MM2.json 25 | This file needs to be customised with your own RPC password and wallet passphrase. If you run the TUI without an MM2.json file, a menu option to create one will be available. 26 | The netid value is set to 9999 by default (used for public beta testing), but can be changed to a different value to allow for testing privately (e.g. between two nodes with a unique shared netid). 27 | ``` 28 | cp MM2_example.json MM2.json 29 | nano MM2.json 30 | ``` 31 | Update the rpc_password, passphrase, userhome and netid (optional) to custom values. 32 | `gui` Leave this as the default value `MM2GUI` 33 | `netid` Defines the network you will trade on. `7777` is being used for released GUI apps and is the most active. 34 | To trade directly with someone on a private network, the seller and buyer can set this to a different custom value. 35 | `rpc_password` Should be at least 12 alphanumeric characters. 36 | This authenticates the user for communicating with the mm2 daemon. 37 | `passphrase` This will be used to generate your wallet addresses. 38 | Should be 12 words minimum, 24 words are recommended. 39 | You can use Luke Child's https://dogeseed.com/ to generate these words (preferably offline). 40 | `userhome` Set this to the home folder of the user installing this repo. 41 | ``` 42 | { 43 | "gui":"MM2GUI", 44 | "netid":9999, 45 | "rpc_password":"ENTER SECURE RPC PASSWORD", 46 | "passphrase":"ENTER A SECURE PASSPHRASE", 47 | "userhome":"/home/YOURUSERNAME/" 48 | } 49 | ``` 50 | 51 | ### lib/coinslib.py 52 | 53 | Contains additional coin parameters to activate them with mm2, defines block explorers, binance trade parameters, and which coins you want to buy/sell using the bot. The default file is populated with 20 mm2 compatible coins, iuncluding those present in the mobile app. 54 | NOTE: You will also need a copy of the `coins` file from https://github.com/jl777/coins/blob/master/coins 55 | 56 | ``` 57 | cd ~/pytomicDEX 58 | wget https://raw.githubusercontent.com/jl777/coins/master/coins 59 | ``` 60 | 61 | Each coin in coinslib.py needs values as per the examples below: 62 | `tag` The ticker for the coin. Must be the same as the `name` value in `coins` value. 63 | `min_swap` Should be higher than the `fee` value in the `coins` file, to ensure you are sending enough funds for a successful transaction. 64 | `api-id` Used to get current pricing data from the CoinGecko API. 65 | Should be the same as the `id` value for the coin from https://www.coingecko.com/api/documentations/v3#/coins/get_coins_list 66 | NOTE: this feature is not yet fully implemented. 67 | `activate_with` Defines whether to use a native coin daemon, or an Electrum (SPV) server. 68 | NOTE: Native mode requires the local chain to be fully sync'd, the deamon running, and the private key for your addresses imported. 69 | `reserve_balance:` number of coins to keep in MM2 wallet (excess will be sent your to Binance wallet) 70 | `premium:` Value relative to Binance market rate to setprices as marketmaker. E.g. a value of 1.05 will set your sell price 5% above Binance market price. 71 | `minQty; maxQty; stepSize:` Values as required for setting orders on Binance, available from https://api.binance.com/api/v1/exchangeInfo 72 | `bot_sell; bot_buy:` Set to True or False to indicate whether or not you want the bot to buy/sell the coin. 73 | `electrum` Defines the server and port to use for interacting with mm2 in lite mode. 74 | Electrum server details can be found at https://github.com/jl777/coins/tree/master/electrums (for Komodo ecosystem coins), and online for external coins (check the coin's website, github or ask the coin community). 75 | Note the different format that is used for ETH/ERC20 tokens in the examples below. 76 | `contract` This value is only required for ETH/ERC20 tokens. 77 | The contract is always `0x8500AFc0bc5214728082163326C2FF0C73f4a871`, which handles atomic swap between ETH/ERC20 tokens and other blockchains. 78 | ETH/ERC20 tokens can all use the same Electrum SPV servers as each other. 79 | NOTE: Trading ETH/ERC20 coins requires a sufficient ETH balance in your mm2 ETH address to cover gas fees. 80 | 81 | ##### Native example *(needs native daemon installed with a sync'd blockchain)* 82 | ```json 83 | "KMD":{ 84 | "min_swap": 0.01, 85 | "api-id": "komodo", 86 | "activate_with":"native", 87 | "tx_explorer":"https://www.kmdexplorer.io/tx", 88 | "reserve_balance":1000, 89 | "premium":1.03, 90 | "min_swap":0.1, 91 | "minQty":"0.01000000", 92 | "maxQty":"90000000.00000000", 93 | "stepSize":"0.01000000", 94 | "bot_sell": True, 95 | "bot_buy": True 96 | }, 97 | ``` 98 | ##### Electrum example 99 | ```json 100 | "KMD":{ 101 | "min_swap": 0.01, 102 | "api-id": "komodo", 103 | "activate_with":"electrum", 104 | "tx_explorer":"https://www.kmdexplorer.io/tx", 105 | "electrum": [{"url":"electrum1.cipig.net:10001"}, 106 | {"url":"electrum2.cipig.net:10001"}, 107 | {"url":"electrum3.cipig.net:10001"}], 108 | "reserve_balance":1000, 109 | "premium":1.03, 110 | "min_swap":0.1, 111 | "minQty":"0.01000000", 112 | "maxQty":"90000000.00000000", 113 | "stepSize":"0.01000000", 114 | "bot_sell": True, 115 | "bot_buy": True 116 | }, 117 | ``` 118 | ##### Electrum example for Etherum and ERC20 tokens 119 | ```json 120 | "ETH":{ 121 | "api-id": "ethereum", 122 | "activate_with":"electrum", 123 | "min_swap": 0.01, 124 | "tx_explorer":"https://etherscan.io/tx", 125 | "electrum": ["http://eth1.cipig.net:8555", 126 | "http://eth2.cipig.net:8555", 127 | "http://eth3.cipig.net:8555"], 128 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 129 | "reserve_balance":2, 130 | "premium":1.03, 131 | "min_swap":0.01, 132 | "minQty":"0.001000000", 133 | "maxQty":"100000.00000000", 134 | "stepSize":"0.001000000", 135 | "bot_sell": True, 136 | "bot_buy": True 137 | }, 138 | ``` 139 | 140 | ### api_keys.json 141 | This file is needed to get prices from the Binance API, and could also be used to manage deposits and withdrawls between your MM2 wallet and Binance wallets (work in progress). 142 | 143 | ```json 144 | { 145 | "binance_key":"YOUR_BINANCE_KEY", 146 | "binance_secret":"YOUR_BINANCE_SECRET" 147 | } 148 | ``` 149 | -------------------------------------------------------------------------------- /lib/binance_api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | from os.path import expanduser 4 | import shutil 5 | import time 6 | import json 7 | import hmac 8 | import hashlib 9 | import requests 10 | import sys 11 | from urllib.parse import urljoin, urlencode 12 | 13 | # Get and set config 14 | cwd = os.getcwd() 15 | home = expanduser("~") 16 | 17 | # from https://code.luasoftware.com/tutorials/cryptocurrency/python-connect-to-binance-api/ 18 | 19 | if not os.path.isfile(sys.path[0]+"/api_keys.json"): 20 | shutil.copy(sys.path[0]+"/api_keys_example.json", sys.path[0]+"/api_keys.json") 21 | 22 | with open(sys.path[0]+"/api_keys.json") as keys_j: 23 | keys_json = json.load(keys_j) 24 | api_key = keys_json['binance_key'] 25 | api_secret = keys_json['binance_secret'] 26 | base_url = 'https://api.binance.com' 27 | 28 | headers = { 29 | 'X-MBX-APIKEY': api_key 30 | } 31 | 32 | class BinanceException(Exception): 33 | def __init__(self, status_code, data): 34 | self.status_code = status_code 35 | if data: 36 | self.code = data['code'] 37 | self.msg = data['msg'] 38 | else: 39 | self.code = None 40 | self.msg = None 41 | message = f"{status_code} [{self.code}] {self.msg}" 42 | super().__init__(message) 43 | 44 | def get_serverTime(): 45 | path = '/api/v1/time' 46 | params = None 47 | 48 | timestamp = int(time.time() * 1000) 49 | 50 | url = urljoin(base_url, path) 51 | r = requests.get(url, params=params) 52 | if r.status_code == 200: 53 | # print(json.dumps(r.json(), indent=2)) 54 | data = r.json() 55 | print(f"diff={timestamp - data['serverTime']}ms") 56 | else: 57 | raise BinanceException(status_code=r.status_code, data=r.json()) 58 | 59 | def get_price(ticker_pair): 60 | path = '/api/v3/ticker/price' 61 | params = { 62 | 'symbol': ticker_pair 63 | } 64 | url = urljoin(base_url, path) 65 | r = requests.get(url, headers=headers, params=params) 66 | return r.json() 67 | 68 | 69 | def get_orderbook(ticker_pair): 70 | path = '/api/v1/depth' 71 | params = { 72 | 'symbol': ticker_pair, 73 | 'limit': 5 74 | } 75 | url = urljoin(base_url, path) 76 | r = requests.get(url, headers=headers, params=params) 77 | if r.status_code == 200: 78 | print(json.dumps(r.json(), indent=2)) 79 | else: 80 | raise BinanceException(status_code=r.status_code, data=r.json()) 81 | 82 | def create_buy_order(ticker_pair, qty, price): 83 | path = '/api/v3/order' 84 | timestamp = int(time.time() * 1000) 85 | params = { 86 | 'symbol': ticker_pair, 87 | 'side': 'BUY', 88 | 'type': 'LIMIT', 89 | 'timeInForce': 'GTC', 90 | 'quantity': qty, 91 | 'price': price, 92 | 'recvWindow': 5000, 93 | 'timestamp': timestamp 94 | } 95 | 96 | query_string = urlencode(params) 97 | params['signature'] = hmac.new(api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest() 98 | 99 | url = urljoin(base_url, path) 100 | r = requests.post(url, headers=headers, params=params) 101 | if r.status_code == 200: 102 | data = r.json() 103 | print(json.dumps(data, indent=2)) 104 | else: 105 | raise BinanceException(status_code=r.status_code, data=r.json()) 106 | 107 | def create_sell_order(ticker_pair, qty, price): 108 | print("Selling "+str(qty)+" "+ticker_pair+" at "+str(price)) 109 | path = '/api/v3/order' 110 | timestamp = int(time.time() * 1000) 111 | params = { 112 | 'symbol': ticker_pair, 113 | 'side': 'SELL', 114 | 'type': 'LIMIT', 115 | 'timeInForce': 'GTC', 116 | 'quantity': qty, 117 | 'price': price, 118 | 'recvWindow': 5000, 119 | 'timestamp': timestamp 120 | } 121 | query_string = urlencode(params) 122 | params['signature'] = hmac.new(api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest() 123 | url = urljoin(base_url, path) 124 | r = requests.post(url, headers=headers, params=params) 125 | if r.status_code == 200: 126 | data = r.json() 127 | print(json.dumps(data, indent=2)) 128 | else: 129 | raise BinanceException(status_code=r.status_code, data=r.json()) 130 | 131 | def get_account_info(): 132 | path = '/api/v3/account' 133 | timestamp = int(time.time() * 1000) 134 | params = { 135 | 'recvWindow': 5000, 136 | 'timestamp': timestamp 137 | } 138 | query_string = urlencode(params) 139 | params['signature'] = hmac.new(api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest() 140 | url = urljoin(base_url, path) 141 | r = requests.get(url, headers=headers, params=params) 142 | return r.json() 143 | 144 | 145 | def get_order(ticker_pair, order_id): 146 | path = '/api/v3/order' 147 | timestamp = int(time.time() * 1000) 148 | params = { 149 | 'symbol': ticker_pair, 150 | 'orderId': order_id, 151 | 'recvWindow': 5000, 152 | 'timestamp': timestamp 153 | } 154 | query_string = urlencode(params) 155 | params['signature'] = hmac.new(api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest() 156 | 157 | url = urljoin(base_url, path) 158 | r = requests.get(url, headers=headers, params=params) 159 | if r.status_code == 200: 160 | data = r.json() 161 | print(json.dumps(data, indent=2)) 162 | else: 163 | raise BinanceException(status_code=r.status_code, data=r.json()) 164 | 165 | def delete_order(ticker_pair, order_id): 166 | path = '/api/v3/order' 167 | timestamp = int(time.time() * 1000) 168 | params = { 169 | 'symbol': ticker_pair, 170 | 'orderId': order_id, 171 | 'recvWindow': 5000, 172 | 'timestamp': timestamp 173 | } 174 | 175 | query_string = urlencode(params) 176 | params['signature'] = hmac.new(api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest() 177 | 178 | url = urljoin(base_url, path) 179 | r = requests.delete(url, headers=headers, params=params) 180 | if r.status_code == 200: 181 | data = r.json() 182 | print(json.dumps(data, indent=2)) 183 | else: 184 | raise BinanceException(status_code=r.status_code, data=r.json()) 185 | 186 | def get_deposit_addr(asset): 187 | path = '/wapi/v3/depositAddress.html' 188 | timestamp = int(time.time() * 1000) 189 | params = { 190 | 'asset': asset, 191 | 'timestamp': timestamp 192 | } 193 | query_string = urlencode(params) 194 | params['signature'] = hmac.new(api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest() 195 | url = urljoin(base_url, path) 196 | r = requests.get(url, headers=headers, params=params) 197 | return r.json() 198 | 199 | def withdraw(asset, addr, amount): 200 | path = '/wapi/v3/withdraw.html' 201 | timestamp = int(time.time() * 1000) 202 | params = { 203 | 'asset': asset, 204 | 'address': addr, 205 | 'amount': amount, 206 | 'timestamp': timestamp 207 | } 208 | query_string = urlencode(params) 209 | params['signature'] = hmac.new(api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest() 210 | url = urljoin(base_url, path) 211 | r = requests.post(url, headers=headers, params=params) 212 | return r.json() 213 | 214 | def round_to_step(coin, qty, stepSize): 215 | # check https://api.binance.com/api/v1/exchangeInfo for stepsize for coin 216 | #Under Symbols Filters LOT_SIZE 217 | return int(float(qty)/float(stepSize))*float(stepSize) 218 | -------------------------------------------------------------------------------- /lib/rpclib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | import time 6 | import requests 7 | import subprocess 8 | from os.path import expanduser 9 | from . import coinslib, tuilib, binance_api 10 | 11 | cwd = os.getcwd() 12 | script_path = sys.path[0] 13 | home = expanduser("~") 14 | 15 | #TODO: change this to match python methods 16 | def help_mm2(node_ip, user_pass): 17 | params = {'userpass': user_pass, 'method': 'help'} 18 | r = requests.post(node_ip, json=params) 19 | return r.text 20 | 21 | def check_mm2_status(node_ip, user_pass): 22 | try: 23 | help_mm2(node_ip, user_pass) 24 | return True 25 | except Exception as e: 26 | return False 27 | 28 | def my_orders(node_ip, user_pass): 29 | params = {'userpass': user_pass, 'method': 'my_orders',} 30 | r = requests.post(node_ip, json=params) 31 | return r 32 | 33 | def version(node_ip, user_pass): 34 | params = {'userpass': user_pass, 'method': 'version',} 35 | r = requests.post(node_ip, json=params) 36 | return r 37 | 38 | def orderbook(node_ip, user_pass, base, rel): 39 | params = {'userpass': user_pass, 40 | 'method': 'orderbook', 41 | 'base': base, 'rel': rel,} 42 | r = requests.post(node_ip, json=params) 43 | return r 44 | 45 | def get_enabled_coins(node_ip, user_pass): 46 | params = {'userpass': user_pass, 47 | 'method': 'get_enabled_coins'} 48 | r = requests.post(node_ip, json=params) 49 | return r 50 | 51 | def check_active_coins(node_ip, user_pass): 52 | active_cointags = [] 53 | active_coins = get_enabled_coins(node_ip, user_pass).json()['result'] 54 | for coin in active_coins: 55 | active_cointags.append(coin['ticker']) 56 | return active_cointags 57 | 58 | def check_coins_status(node_ip, user_pass): 59 | if os.path.exists(script_path+"/coins") is False: 60 | print(tuilib.colorize("\n'coins' file not found in "+script_path+"!",'red')) 61 | print(tuilib.colorize("Use 'wget https://raw.githubusercontent.com/jl777/coins/master/coins' to download.", 'orange')) 62 | print(tuilib.colorize("Exiting...\n", 'blue')) 63 | sys.exit() 64 | elif os.path.exists(script_path+"/api_keys.json") is False: 65 | print(tuilib.colorize("\n'api_keys.json' file not found in "+script_path+"!",'red')) 66 | print(tuilib.colorize("Use 'cp api_keys_example.json api_keys.json' to copy it.", 'orange')) 67 | print(tuilib.colorize("You can leave the values blank, or input your own Binance API keys", 'orange')) 68 | print(tuilib.colorize("Exiting...\n", 'blue')) 69 | sys.exit() 70 | else: 71 | cointag_list = [] 72 | for coin in coinslib.coins: 73 | cointag_list.append(coin) 74 | cointag_list.sort() 75 | num_all_coins = len(cointag_list) 76 | active_coins = check_active_coins(node_ip, user_pass) 77 | num_active_coins = len(active_coins) 78 | msg = str(num_active_coins)+"/"+str(num_all_coins)+" coins active" 79 | if num_active_coins == 0: 80 | color = 'red' 81 | all_active = False 82 | elif num_active_coins < len(coinslib.coins): 83 | color = 'yellow' 84 | all_active = True 85 | else: 86 | color = 'green' 87 | all_active = True 88 | return msg, color, all_active, active_coins 89 | 90 | def get_status(node_ip, user_pass): 91 | mm2_active = check_mm2_status(node_ip, user_pass) 92 | if mm2_active: 93 | version_txt = version(node_ip, user_pass).json() 94 | try: 95 | ver = "v"+version_txt['result'].split("_")[0] 96 | except: 97 | ver = '' 98 | pass 99 | mm2_msg = tuilib.colorize("[MM2 "+ver+" active]", 'green') 100 | coins_status = check_coins_status(node_ip, user_pass) 101 | my_current_orders = my_orders(node_ip, user_pass).json()['result'] 102 | num_orders = len(my_current_orders['maker_orders']) + len(my_current_orders['taker_orders']) 103 | coin_msg = tuilib.colorize("["+coins_status[0]+"]", coins_status[1]) 104 | status_msg = mm2_msg+" "+coin_msg 105 | else: 106 | mm2_msg = tuilib.colorize("[MM2 disabled]", 'red') 107 | num_orders = 0 108 | status_msg = '' 109 | coins_status = ['','','',''] 110 | return status_msg, mm2_active, coins_status[2], coins_status[3], num_orders 111 | 112 | def enable(node_ip, user_pass, cointag, tx_history=True): 113 | coin = coinslib.coins[cointag] 114 | params = {'userpass': user_pass, 115 | 'method': 'enable', 116 | 'coin': cointag, 117 | 'mm2':1, 118 | 'tx_history':tx_history,} 119 | r = requests.post(node_ip, json=params) 120 | return r 121 | 122 | def electrum(node_ip, user_pass, cointag, tx_history=True): 123 | coin = coinslib.coins[cointag] 124 | if 'contract' in coin: 125 | params = {'userpass': user_pass, 126 | 'method': 'enable', 127 | 'urls':coin['electrum'], 128 | 'coin': cointag, 129 | 'swap_contract_address': coin['contract'], 130 | 'mm2':1, 131 | 'tx_history':tx_history,} 132 | else: 133 | params = {'userpass': user_pass, 134 | 'method': 'electrum', 135 | 'servers':coin['electrum'], 136 | 'coin': cointag, 137 | 'mm2':1, 138 | 'tx_history':tx_history,} 139 | print(params) 140 | r = requests.post(node_ip, json=params) 141 | return r 142 | 143 | def buy(node_ip, user_pass, base, rel, basevolume, relprice): 144 | params ={'userpass': user_pass, 145 | 'method': 'buy', 146 | 'base': base, 147 | 'rel': rel, 148 | 'volume': basevolume, 149 | 'price': relprice,} 150 | r = requests.post(node_ip,json=params) 151 | return r 152 | 153 | def my_balance(node_ip, user_pass, cointag): 154 | params = {'userpass': user_pass, 155 | 'method': 'my_balance', 156 | 'coin': cointag,} 157 | r = requests.post(node_ip, json=params) 158 | return r 159 | 160 | 161 | def cancel_all(node_ip, user_pass): 162 | params = {'userpass': user_pass, 163 | 'method': 'cancel_all_orders', 164 | 'cancel_by': {"type":"All"},} 165 | r = requests.post(node_ip,json=params) 166 | return r 167 | 168 | def cancel_uuid(node_ip, user_pass, order_uuid): 169 | params = {'userpass': user_pass, 170 | 'method': 'cancel_order', 171 | 'uuid': order_uuid,} 172 | r = requests.post(node_ip,json=params) 173 | return r 174 | 175 | 176 | def get_fee(node_ip, user_pass, coin): 177 | params = {'userpass': user_pass, 178 | 'method': 'get_trade_fee', 179 | 'coin': coin 180 | } 181 | r = requests.post(node_ip,json=params) 182 | return r 183 | 184 | def cancel_pair(node_ip, user_pass, base, rel): 185 | params = {'userpass': user_pass, 186 | 'method': 'cancel_all_orders', 187 | 'cancel_by': { 188 | "type":"Pair", 189 | "data":{"base":base,"rel":rel}, 190 | },} 191 | r = requests.post(node_ip,json=params) 192 | return r 193 | 194 | # sell base, buy rel. 195 | def setprice(node_ip, user_pass, base, rel, basevolume, relprice, trademax=False, cancel_previous=True): 196 | params = {'userpass': user_pass, 197 | 'method': 'setprice', 198 | 'base': base, 199 | 'rel': rel, 200 | 'volume': basevolume, 201 | 'price': relprice, 202 | 'max':trademax, 203 | 'cancel_previous':cancel_previous,} 204 | r = requests.post(node_ip, json=params) 205 | return r 206 | 207 | def recover_stuck_swap(node_ip, user_pass, uuid): 208 | params = {'userpass': user_pass, 209 | 'method': 'recover_funds_of_swap', 210 | 'params': {'uuid':uuid} 211 | } 212 | r = requests.post(node_ip, json=params) 213 | return r 214 | 215 | def withdraw(node_ip, user_pass, cointag, address, amount): 216 | params = {'userpass': user_pass, 217 | 'method': 'withdraw', 218 | 'coin': cointag, 219 | 'to': address, 220 | 'amount': amount,} 221 | r = requests.post(node_ip, json=params) 222 | return r 223 | 224 | def withdraw_all(node_ip, user_pass, cointag, address): 225 | params = {'userpass': user_pass, 226 | 'method': 'withdraw', 227 | 'coin': cointag, 228 | 'to': address, 229 | 'max': True} 230 | r = requests.post(node_ip, json=params) 231 | return r 232 | 233 | def send_raw_transaction(node_ip, user_pass, cointag, rawhex): 234 | params = {'userpass': user_pass, 235 | 'method': 'send_raw_transaction', 236 | 'coin': cointag, "tx_hex":rawhex,} 237 | r = requests.post(node_ip, json=params) 238 | return r 239 | 240 | def my_recent_swaps(node_ip, user_pass, limit=10, from_uuid=''): 241 | if from_uuid=='': 242 | params = {'userpass': user_pass, 243 | 'method': 'my_recent_swaps', 244 | 'limit': int(limit),} 245 | 246 | else: 247 | params = {'userpass': user_pass, 248 | 'method': 'my_recent_swaps', 249 | "limit": int(limit), 250 | "from_uuid":from_uuid,} 251 | r = requests.post(node_ip,json=params) 252 | return r 253 | 254 | def build_coins_data(node_ip, user_pass, cointag_list=''): 255 | try: 256 | if cointag_list == '': 257 | cointag_list = coinslib.cointags 258 | if 'KMD' not in cointag_list: 259 | cointag_list.append('KMD') 260 | coins_data = {} 261 | cointags = [] 262 | gecko_ids = [] 263 | print(tuilib.colorize('Getting prices from Binance...', 'yellow')) 264 | for coin in cointag_list: 265 | coins_data[coin] = {} 266 | cointags.append(coin) 267 | coins_data[coin]['BTC_price'] = float(tuilib.get_btc_price(coin)) 268 | coins_data[coin]['price_source'] = 'binance' 269 | time.sleep(0.05) 270 | # Get Coingecko API ids 271 | print(tuilib.colorize('Getting prices from CoinGecko...', 'pink')) 272 | gecko_coins_list = requests.get(url='https://api.coingecko.com/api/v3/coins/list').json() 273 | for gecko_coin in gecko_coins_list: 274 | try: 275 | if gecko_coin['symbol'].upper() in cointags: 276 | # override to avoid batcoin and dex 277 | if gecko_coin['symbol'].upper() == 'BAT': 278 | coins_data[gecko_coin['symbol'].upper()]['gecko_id'] = 'basic-attention-token' 279 | gecko_ids.append('basic-attention-token') 280 | elif gecko_coin['symbol'].upper() in ['DEX', 'CRYPTO']: 281 | pass 282 | else: 283 | coins_data[gecko_coin['symbol'].upper()]['gecko_id'] = gecko_coin['id'] 284 | gecko_ids.append(gecko_coin['id']) 285 | except Exception as e: 286 | print(colorize("error getting coingecko price for "+gecko_coin, 'red')) 287 | print(colorize(e, 'red')) 288 | pass 289 | # Get fiat price on Coingecko 290 | gecko_prices = gecko_fiat_prices(",".join(gecko_ids), 'usd,aud,btc').json() 291 | for coin_id in gecko_prices: 292 | for coin in coins_data: 293 | if 'gecko_id' in coins_data[coin]: 294 | if coins_data[coin]['gecko_id'] == coin_id: 295 | coins_data[coin]['AUD_price'] = gecko_prices[coin_id]['aud'] 296 | coins_data[coin]['USD_price'] = gecko_prices[coin_id]['usd'] 297 | if coins_data[coin]['BTC_price'] == 0: 298 | coins_data[coin]['BTC_price'] = gecko_prices[coin_id]['btc'] 299 | coins_data[coin]['price_source'] = 'coingecko' 300 | else: 301 | coins_data[coin]['AUD_price'] = 0 302 | coins_data[coin]['USD_price'] = 0 303 | print(tuilib.colorize('Getting prices from mm2 orderbook...', 'cyan')) 304 | for coin in coins_data: 305 | try: 306 | if coin == 'RICK' or coin == 'MORTY': 307 | coins_data[coin]['BTC_price'] = 0 308 | coins_data[coin]['AUD_price'] = 0 309 | coins_data[coin]['USD_price'] = 0 310 | coins_data[coin]['KMD_price'] = 0 311 | coins_data[coin]['price_source'] = 'mm2_orderbook' 312 | elif coins_data[coin]['BTC_price'] == 0: 313 | mm2_kmd_price = get_kmd_mm2_price(node_ip, user_pass, coin) 314 | coins_data[coin]['KMD_price'] = mm2_kmd_price[1] 315 | coins_data[coin]['price_source'] = 'mm2_orderbook' 316 | coins_data[coin]['BTC_price'] = mm2_kmd_price[1]*coins_data['KMD']['BTC_price'] 317 | coins_data[coin]['AUD_price'] = mm2_kmd_price[1]*coins_data['KMD']['AUD_price'] 318 | coins_data[coin]['USD_price'] = mm2_kmd_price[1]*coins_data['KMD']['USD_price'] 319 | except Exception as e: 320 | print("Error getting KMD price: "+str(e)) 321 | coins_data[coin]['KMD_price'] = 0 322 | coins_data[coin]['price_source'] = 'mm2_orderbook' 323 | coins_data[coin]['BTC_price'] = 0 324 | coins_data[coin]['AUD_price'] = 0 325 | coins_data[coin]['USD_price'] = 0 326 | except Exception as e: 327 | print("Error getting coins_data: "+str(e)) 328 | return coins_data 329 | 330 | def gecko_fiat_prices(gecko_ids, fiat): 331 | url = 'https://api.coingecko.com/api/v3/simple/price' 332 | params = dict(ids=str(gecko_ids),vs_currencies=fiat) 333 | r = requests.get(url=url, params=params) 334 | return r 335 | 336 | def get_kmd_mm2_price(node_ip, user_pass, coin): 337 | kmd_orders = orderbook(node_ip, user_pass, coin, 'KMD').json() 338 | kmd_value = 0 339 | min_kmd_value = 999999999999999999 340 | total_kmd_value = 0 341 | max_kmd_value = 0 342 | kmd_volume = 0 343 | num_asks = 0 344 | if 'asks' in kmd_orders: 345 | num_asks = len(kmd_orders['asks']) 346 | for asks in kmd_orders['asks']: 347 | kmd_value = float(asks['maxvolume']) * float(asks['price']) 348 | if kmd_value < min_kmd_value: 349 | min_kmd_value = kmd_value 350 | elif kmd_value > max_kmd_value: 351 | max_kmd_value = kmd_value 352 | total_kmd_value += kmd_value 353 | kmd_volume += float(asks['maxvolume']) 354 | else: 355 | print(kmd_orders) 356 | if num_asks > 0: 357 | median_kmd_value = total_kmd_value/kmd_volume 358 | else: 359 | median_kmd_value = 0 360 | return min_kmd_value, median_kmd_value, max_kmd_value 361 | 362 | def my_swap_status(node_ip, user_pass, swap_uuid): 363 | params = {'userpass': user_pass, 364 | 'method': 'my_swap_status', 365 | 'params': {"uuid": swap_uuid},} 366 | r = requests.post(node_ip,json=params) 367 | return r 368 | 369 | def get_unfinished_swaps(node_ip, user_pass): 370 | unfinished_swaps = [] 371 | unfinished_swap_uuids = [] 372 | recent_swaps = my_recent_swaps(node_ip, user_pass, 50).json() 373 | for swap in recent_swaps['result']['swaps']: 374 | swap_events = [] 375 | for event in swap['events']: 376 | swap_events.append(event['event']['type']) 377 | if 'Finished' not in swap_events: 378 | unfinished_swaps.append(swap) 379 | unfinished_swap_uuids.append(swap['uuid']) 380 | return unfinished_swap_uuids, unfinished_swaps 381 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2017 The Bitcoin Core developers 2 | Copyright (c) 2009-2018 Bitcoin Developers 3 | Copyright (c) 2016-2017 The Zcash developers 4 | Copyright (c) 2016-2019 The Komodo developers 5 | Copyright (c) 2018 The VerusCoin developers 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | 26 | The MIT software license (http://www.opensource.org/licenses/mit-license.php) 27 | above applies to the code directly included in this source distribution. 28 | Dependencies downloaded as part of the build process may be covered by other 29 | open-source licenses. For further details see 'contrib/debian/copyright'. 30 | 31 | 32 | This product includes software developed by the OpenSSL Project for use in the 33 | OpenSSL Toolkit (https://www.openssl.org/). This product includes cryptographic 34 | software written by Eric Young (eay@cryptsoft.com). 35 | 36 | 37 | Although almost all of the Zcash/Komodo/VerusCoin code is licensed under "permissive" open source 38 | licenses, users and distributors should note that when built using the default 39 | build options, Zcash depends on Oracle Berkeley DB 6.2.x, which is licensed 40 | under the GNU Affero General Public License. 41 | 42 | SuperNET COPYING terms: 43 | GNU GENERAL PUBLIC LICENSE 44 | Version 2, June 1991 45 | 46 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 47 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 48 | Everyone is permitted to copy and distribute verbatim copies 49 | of this license document, but changing it is not allowed. 50 | 51 | Preamble 52 | 53 | The licenses for most software are designed to take away your 54 | freedom to share and change it. By contrast, the GNU General Public 55 | License is intended to guarantee your freedom to share and change free 56 | software--to make sure the software is free for all its users. This 57 | General Public License applies to most of the Free Software 58 | Foundation's software and to any other program whose authors commit to 59 | using it. (Some other Free Software Foundation software is covered by 60 | the GNU Lesser General Public License instead.) You can apply it to 61 | your programs, too. 62 | 63 | When we speak of free software, we are referring to freedom, not 64 | price. Our General Public Licenses are designed to make sure that you 65 | have the freedom to distribute copies of free software (and charge for 66 | this service if you wish), that you receive source code or can get it 67 | if you want it, that you can change the software or use pieces of it 68 | in new free programs; and that you know you can do these things. 69 | 70 | To protect your rights, we need to make restrictions that forbid 71 | anyone to deny you these rights or to ask you to surrender the rights. 72 | These restrictions translate to certain responsibilities for you if you 73 | distribute copies of the software, or if you modify it. 74 | 75 | For example, if you distribute copies of such a program, whether 76 | gratis or for a fee, you must give the recipients all the rights that 77 | you have. You must make sure that they, too, receive or can get the 78 | source code. And you must show them these terms so they know their 79 | rights. 80 | 81 | We protect your rights with two steps: (1) copyright the software, and 82 | (2) offer you this license which gives you legal permission to copy, 83 | distribute and/or modify the software. 84 | 85 | Also, for each author's protection and ours, we want to make certain 86 | that everyone understands that there is no warranty for this free 87 | software. If the software is modified by someone else and passed on, we 88 | want its recipients to know that what they have is not the original, so 89 | that any problems introduced by others will not reflect on the original 90 | authors' reputations. 91 | 92 | Finally, any free program is threatened constantly by software 93 | patents. We wish to avoid the danger that redistributors of a free 94 | program will individually obtain patent licenses, in effect making the 95 | program proprietary. To prevent this, we have made it clear that any 96 | patent must be licensed for everyone's free use or not licensed at all. 97 | 98 | The precise terms and conditions for copying, distribution and 99 | modification follow. 100 | 101 | GNU GENERAL PUBLIC LICENSE 102 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 103 | 104 | 0. This License applies to any program or other work which contains 105 | a notice placed by the copyright holder saying it may be distributed 106 | under the terms of this General Public License. The "Program", below, 107 | refers to any such program or work, and a "work based on the Program" 108 | means either the Program or any derivative work under copyright law: 109 | that is to say, a work containing the Program or a portion of it, 110 | either verbatim or with modifications and/or translated into another 111 | language. (Hereinafter, translation is included without limitation in 112 | the term "modification".) Each licensee is addressed as "you". 113 | 114 | Activities other than copying, distribution and modification are not 115 | covered by this License; they are outside its scope. The act of 116 | running the Program is not restricted, and the output from the Program 117 | is covered only if its contents constitute a work based on the 118 | Program (independent of having been made by running the Program). 119 | Whether that is true depends on what the Program does. 120 | 121 | 1. You may copy and distribute verbatim copies of the Program's 122 | source code as you receive it, in any medium, provided that you 123 | conspicuously and appropriately publish on each copy an appropriate 124 | copyright notice and disclaimer of warranty; keep intact all the 125 | notices that refer to this License and to the absence of any warranty; 126 | and give any other recipients of the Program a copy of this License 127 | along with the Program. 128 | 129 | You may charge a fee for the physical act of transferring a copy, and 130 | you may at your option offer warranty protection in exchange for a fee. 131 | 132 | 2. You may modify your copy or copies of the Program or any portion 133 | of it, thus forming a work based on the Program, and copy and 134 | distribute such modifications or work under the terms of Section 1 135 | above, provided that you also meet all of these conditions: 136 | 137 | a) You must cause the modified files to carry prominent notices 138 | stating that you changed the files and the date of any change. 139 | 140 | b) You must cause any work that you distribute or publish, that in 141 | whole or in part contains or is derived from the Program or any 142 | part thereof, to be licensed as a whole at no charge to all third 143 | parties under the terms of this License. 144 | 145 | c) If the modified program normally reads commands interactively 146 | when run, you must cause it, when started running for such 147 | interactive use in the most ordinary way, to print or display an 148 | announcement including an appropriate copyright notice and a 149 | notice that there is no warranty (or else, saying that you provide 150 | a warranty) and that users may redistribute the program under 151 | these conditions, and telling the user how to view a copy of this 152 | License. (Exception: if the Program itself is interactive but 153 | does not normally print such an announcement, your work based on 154 | the Program is not required to print an announcement.) 155 | 156 | These requirements apply to the modified work as a whole. If 157 | identifiable sections of that work are not derived from the Program, 158 | and can be reasonably considered independent and separate works in 159 | themselves, then this License, and its terms, do not apply to those 160 | sections when you distribute them as separate works. But when you 161 | distribute the same sections as part of a whole which is a work based 162 | on the Program, the distribution of the whole must be on the terms of 163 | this License, whose permissions for other licensees extend to the 164 | entire whole, and thus to each and every part regardless of who wrote it. 165 | 166 | Thus, it is not the intent of this section to claim rights or contest 167 | your rights to work written entirely by you; rather, the intent is to 168 | exercise the right to control the distribution of derivative or 169 | collective works based on the Program. 170 | 171 | In addition, mere aggregation of another work not based on the Program 172 | with the Program (or with a work based on the Program) on a volume of 173 | a storage or distribution medium does not bring the other work under 174 | the scope of this License. 175 | 176 | 3. You may copy and distribute the Program (or a work based on it, 177 | under Section 2) in object code or executable form under the terms of 178 | Sections 1 and 2 above provided that you also do one of the following: 179 | 180 | a) Accompany it with the complete corresponding machine-readable 181 | source code, which must be distributed under the terms of Sections 182 | 1 and 2 above on a medium customarily used for software interchange; or, 183 | 184 | b) Accompany it with a written offer, valid for at least three 185 | years, to give any third party, for a charge no more than your 186 | cost of physically performing source distribution, a complete 187 | machine-readable copy of the corresponding source code, to be 188 | distributed under the terms of Sections 1 and 2 above on a medium 189 | customarily used for software interchange; or, 190 | 191 | c) Accompany it with the information you received as to the offer 192 | to distribute corresponding source code. (This alternative is 193 | allowed only for noncommercial distribution and only if you 194 | received the program in object code or executable form with such 195 | an offer, in accord with Subsection b above.) 196 | 197 | The source code for a work means the preferred form of the work for 198 | making modifications to it. For an executable work, complete source 199 | code means all the source code for all modules it contains, plus any 200 | associated interface definition files, plus the scripts used to 201 | control compilation and installation of the executable. However, as a 202 | special exception, the source code distributed need not include 203 | anything that is normally distributed (in either source or binary 204 | form) with the major components (compiler, kernel, and so on) of the 205 | operating system on which the executable runs, unless that component 206 | itself accompanies the executable. 207 | 208 | If distribution of executable or object code is made by offering 209 | access to copy from a designated place, then offering equivalent 210 | access to copy the source code from the same place counts as 211 | distribution of the source code, even though third parties are not 212 | compelled to copy the source along with the object code. 213 | 214 | 4. You may not copy, modify, sublicense, or distribute the Program 215 | except as expressly provided under this License. Any attempt 216 | otherwise to copy, modify, sublicense or distribute the Program is 217 | void, and will automatically terminate your rights under this License. 218 | However, parties who have received copies, or rights, from you under 219 | this License will not have their licenses terminated so long as such 220 | parties remain in full compliance. 221 | 222 | 5. You are not required to accept this License, since you have not 223 | signed it. However, nothing else grants you permission to modify or 224 | distribute the Program or its derivative works. These actions are 225 | prohibited by law if you do not accept this License. Therefore, by 226 | modifying or distributing the Program (or any work based on the 227 | Program), you indicate your acceptance of this License to do so, and 228 | all its terms and conditions for copying, distributing or modifying 229 | the Program or works based on it. 230 | 231 | 6. Each time you redistribute the Program (or any work based on the 232 | Program), the recipient automatically receives a license from the 233 | original licensor to copy, distribute or modify the Program subject to 234 | these terms and conditions. You may not impose any further 235 | restrictions on the recipients' exercise of the rights granted herein. 236 | You are not responsible for enforcing compliance by third parties to 237 | this License. 238 | 239 | 7. If, as a consequence of a court judgment or allegation of patent 240 | infringement or for any other reason (not limited to patent issues), 241 | conditions are imposed on you (whether by court order, agreement or 242 | otherwise) that contradict the conditions of this License, they do not 243 | excuse you from the conditions of this License. If you cannot 244 | distribute so as to satisfy simultaneously your obligations under this 245 | License and any other pertinent obligations, then as a consequence you 246 | may not distribute the Program at all. For example, if a patent 247 | license would not permit royalty-free redistribution of the Program by 248 | all those who receive copies directly or indirectly through you, then 249 | the only way you could satisfy both it and this License would be to 250 | refrain entirely from distribution of the Program. 251 | 252 | If any portion of this section is held invalid or unenforceable under 253 | any particular circumstance, the balance of the section is intended to 254 | apply and the section as a whole is intended to apply in other 255 | circumstances. 256 | 257 | It is not the purpose of this section to induce you to infringe any 258 | patents or other property right claims or to contest validity of any 259 | such claims; this section has the sole purpose of protecting the 260 | integrity of the free software distribution system, which is 261 | implemented by public license practices. Many people have made 262 | generous contributions to the wide range of software distributed 263 | through that system in reliance on consistent application of that 264 | system; it is up to the author/donor to decide if he or she is willing 265 | to distribute software through any other system and a licensee cannot 266 | impose that choice. 267 | 268 | This section is intended to make thoroughly clear what is believed to 269 | be a consequence of the rest of this License. 270 | 271 | 8. If the distribution and/or use of the Program is restricted in 272 | certain countries either by patents or by copyrighted interfaces, the 273 | original copyright holder who places the Program under this License 274 | may add an explicit geographical distribution limitation excluding 275 | those countries, so that distribution is permitted only in or among 276 | countries not thus excluded. In such case, this License incorporates 277 | the limitation as if written in the body of this License. 278 | 279 | 9. The Free Software Foundation may publish revised and/or new versions 280 | of the General Public License from time to time. Such new versions will 281 | be similar in spirit to the present version, but may differ in detail to 282 | address new problems or concerns. 283 | 284 | Each version is given a distinguishing version number. If the Program 285 | specifies a version number of this License which applies to it and "any 286 | later version", you have the option of following the terms and conditions 287 | either of that version or of any later version published by the Free 288 | Software Foundation. If the Program does not specify a version number of 289 | this License, you may choose any version ever published by the Free Software 290 | Foundation. 291 | 292 | 10. If you wish to incorporate parts of the Program into other free 293 | programs whose distribution conditions are different, write to the author 294 | to ask for permission. For software which is copyrighted by the Free 295 | Software Foundation, write to the Free Software Foundation; we sometimes 296 | make exceptions for this. Our decision will be guided by the two goals 297 | of preserving the free status of all derivatives of our free software and 298 | of promoting the sharing and reuse of software generally. 299 | 300 | NO WARRANTY 301 | 302 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 303 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 304 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 305 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 306 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 307 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 308 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 309 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 310 | REPAIR OR CORRECTION. 311 | 312 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 313 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 314 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 315 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 316 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 317 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 318 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 319 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 320 | POSSIBILITY OF SUCH DAMAGES. 321 | 322 | END OF TERMS AND CONDITIONS 323 | 324 | How to Apply These Terms to Your New Programs 325 | 326 | If you develop a new program, and you want it to be of the greatest 327 | possible use to the public, the best way to achieve this is to make it 328 | free software which everyone can redistribute and change under these terms. 329 | 330 | To do so, attach the following notices to the program. It is safest 331 | to attach them to the start of each source file to most effectively 332 | convey the exclusion of warranty; and each file should have at least 333 | the "copyright" line and a pointer to where the full notice is found. 334 | 335 | 336 | Copyright (C) 337 | 338 | This program is free software; you can redistribute it and/or modify 339 | it under the terms of the GNU General Public License as published by 340 | the Free Software Foundation; either version 2 of the License, or 341 | (at your option) any later version. 342 | 343 | This program is distributed in the hope that it will be useful, 344 | but WITHOUT ANY WARRANTY; without even the implied warranty of 345 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 346 | GNU General Public License for more details. 347 | 348 | You should have received a copy of the GNU General Public License along 349 | with this program; if not, write to the Free Software Foundation, Inc., 350 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 351 | 352 | Also add information on how to contact you by electronic and paper mail. 353 | 354 | If the program is interactive, make it output a short notice like this 355 | when it starts in an interactive mode: 356 | 357 | Gnomovision version 69, Copyright (C) year name of author 358 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 359 | This is free software, and you are welcome to redistribute it 360 | under certain conditions; type `show c' for details. 361 | 362 | The hypothetical commands `show w' and `show c' should show the appropriate 363 | parts of the General Public License. Of course, the commands you use may 364 | be called something other than `show w' and `show c'; they could even be 365 | mouse-clicks or menu items--whatever suits your program. 366 | 367 | You should also get your employer (if you work as a programmer) or your 368 | school, if any, to sign a "copyright disclaimer" for the program, if 369 | necessary. Here is a sample; alter the names: 370 | 371 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 372 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 373 | 374 | , 1 April 1989 375 | Ty Coon, President of Vice 376 | 377 | This General Public License does not permit incorporating your program into 378 | proprietary programs. If your program is a subroutine library, you may 379 | consider it more useful to permit linking proprietary applications with the 380 | library. If this is what you want to do, use the GNU Lesser General 381 | Public License instead of this License. 382 | -------------------------------------------------------------------------------- /lib/coinslib.py: -------------------------------------------------------------------------------- 1 | coins = { 2 | "AXE":{ 3 | "min_swap": 1, 4 | "api-id": "axe", 5 | "activate_with":"electrum", 6 | "reserve_balance":2, 7 | "tx_explorer":"https://etherscan.io/tx", 8 | "electrum": [{"url":"electrum1.cipig.net:10057"}, 9 | {"url":"electrum2.cipig.net:10057"}, 10 | {"url":"electrum3.cipig.net:10057"}], 11 | "bot_sell": False, 12 | "bot_buy": False 13 | }, 14 | "BAT":{ 15 | "min_swap": 0.5, 16 | "activate_with":"electrum", 17 | "api-id": "basic-attention-token", 18 | "tx_explorer":"https://etherscan.io/tx", 19 | "electrum": ["http://eth1.cipig.net:8555", 20 | "http://eth2.cipig.net:8555", 21 | "http://eth3.cipig.net:8555"], 22 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 23 | "reserve_balance":500, 24 | "premium":1.03, 25 | "min_swap":1, 26 | "minQty":"1.00000000", 27 | "maxQty":"90000000.00000000", 28 | "stepSize":"1.00000000", 29 | "bot_sell": True, 30 | "bot_buy": True 31 | }, 32 | "AWC":{ 33 | "min_swap": 0.5, 34 | "activate_with":"electrum", 35 | "api-id": "", 36 | "tx_explorer":"https://etherscan.io/tx", 37 | "electrum": ["http://eth1.cipig.net:8555", 38 | "http://eth2.cipig.net:8555", 39 | "http://eth3.cipig.net:8555"], 40 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 41 | "bot_sell": True, 42 | "bot_buy": True 43 | }, 44 | "BCH":{ 45 | "min_swap": 0.01, 46 | "activate_with":"electrum", 47 | "tx_explorer":"https://explorer.bitcoin.com/bch/tx", 48 | "electrum": [{"url":"electron.coinucopia.io:50001"}, 49 | {"url":"bch.imaginary.cash:50001"}, 50 | {"url":"wallet.satoshiscoffeehouse.com:50001"}, 51 | {"url":"electroncash.dk:50001"}, 52 | {"url":"electron-cash.dragon.zone:50001"}], 53 | "reserve_balance":1, 54 | "premium":1.03, 55 | "min_swap":0.01, 56 | "minQty":"0.00001000", 57 | "maxQty":"900000.00000000", 58 | "stepSize":"0.00001000", 59 | "bot_sell": True, 60 | "bot_buy": True 61 | }, 62 | "BET":{ 63 | "min_swap": 0.1, 64 | "api-id": "", 65 | "activate_with":"electrum", 66 | "reserve_balance":2, 67 | "tx_explorer":"https://bet.explorer.dexstats.info/tx", 68 | "electrum": [{"url":"electrum1.cipig.net:10012"}, 69 | {"url":"electrum2.cipig.net:10012"}, 70 | {"url":"electrum3.cipig.net:10012"}], 71 | "bot_sell": False, 72 | "bot_buy": False 73 | }, 74 | "BOTS":{ 75 | "min_swap": 0.1, 76 | "api-id": "", 77 | "activate_with":"electrum", 78 | "reserve_balance":2, 79 | "tx_explorer":"https://bots.explorer.dexstats.info/tx", 80 | "electrum": [{"url":"electrum1.cipig.net:10007"}, 81 | {"url":"electrum2.cipig.net:10007"}, 82 | {"url":"electrum3.cipig.net:10007"}], 83 | "bot_sell": False, 84 | "bot_buy": False 85 | }, 86 | "BTC":{ 87 | "api-id": "bitcoin", 88 | "activate_with":"electrum", 89 | "reserve_balance":0.02, 90 | "tx_explorer":"https://explorer.bitcoin.com/btc/tx", 91 | "electrum": [{"url":"electrum1.cipig.net:10000"}, 92 | {"url":"electrum2.cipig.net:10000"}, 93 | {"url":"electrum3.cipig.net:10000"}], 94 | "bot_sell": False, 95 | "bot_buy": True 96 | }, 97 | "BTCH":{ 98 | "min_swap": 0.1, 99 | "api-id": "bitcoin-hush", 100 | "activate_with":"electrum", 101 | "reserve_balance":2, 102 | "tx_explorer":"https://bots.explorer.dexstats.info/tx", 103 | "electrum": [{"url":"electrum1.cipig.net:10020"}, 104 | {"url":"electrum2.cipig.net:10020"}, 105 | {"url":"electrum3.cipig.net:10020"}], 106 | "bot_sell": False, 107 | "bot_buy": False 108 | }, 109 | "BUSD":{ 110 | "min_swap": 0.5, 111 | "activate_with":"electrum", 112 | "api-id": "", 113 | "tx_explorer":"https://etherscan.io/tx", 114 | "electrum": ["http://eth1.cipig.net:8555", 115 | "http://eth2.cipig.net:8555", 116 | "http://eth3.cipig.net:8555"], 117 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 118 | "bot_sell": True, 119 | "bot_buy": True 120 | }, 121 | "DAI":{ 122 | "min_swap": 0.5, 123 | "activate_with":"electrum", 124 | "api-id": "", 125 | "tx_explorer":"https://etherscan.io/tx", 126 | "electrum": ["http://eth1.cipig.net:8555", 127 | "http://eth2.cipig.net:8555", 128 | "http://eth3.cipig.net:8555"], 129 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 130 | "bot_sell": True, 131 | "bot_buy": True 132 | }, 133 | "CHIPS":{ 134 | "min_swap": 0.1, 135 | "api-id": "", 136 | "activate_with":"electrum", 137 | "reserve_balance":2, 138 | "tx_explorer":"https://chips.explorer.dexstats.info/tx", 139 | "electrum": [{"url":"electrum1.cipig.net:10053"}, 140 | {"url":"electrum2.cipig.net:10053"}, 141 | {"url":"electrum3.cipig.net:10053"}], 142 | "bot_sell": False, 143 | "bot_buy": False 144 | }, 145 | "CCL":{ 146 | "min_swap": 0.1, 147 | "api-id": "", 148 | "activate_with":"electrum", 149 | "reserve_balance":2, 150 | "tx_explorer":"https://commod.explorer.dexstats.info/tx", 151 | "electrum": [{"url":"electrum1.cipig.net:10029"}, 152 | {"url":"electrum2.cipig.net:10029"}, 153 | {"url":"electrum3.cipig.net:10029"}], 154 | "bot_sell": False, 155 | "bot_buy": False 156 | }, 157 | "COQUI":{ 158 | "min_swap": 0.1, 159 | "api-id": "", 160 | "activate_with":"electrum", 161 | "reserve_balance":2, 162 | "tx_explorer":"https://coqui.explorer.dexstats.info/tx", 163 | "electrum": [{"url":"electrum1.cipig.net:10011"}, 164 | {"url":"electrum2.cipig.net:10011"}, 165 | {"url":"electrum3.cipig.net:10011"}], 166 | "bot_sell": False, 167 | "bot_buy": False 168 | }, 169 | "CRYPTO":{ 170 | "min_swap": 0.1, 171 | "api-id": "", 172 | "activate_with":"electrum", 173 | "reserve_balance":2, 174 | "tx_explorer":"https://crypto.explorer.dexstats.info/tx", 175 | "electrum": [{"url":"electrum1.cipig.net:10008"}, 176 | {"url":"electrum2.cipig.net:10008"}, 177 | {"url":"electrum3.cipig.net:10008"}], 178 | "bot_sell": False, 179 | "bot_buy": False 180 | }, 181 | "DAI":{ 182 | "min_swap": 0.5, 183 | "api-id": "dai", 184 | "activate_with":"electrum", 185 | "reserve_balance":2, 186 | "tx_explorer":"https://etherscan.io/tx", 187 | "electrum": ["http://eth1.cipig.net:8555", 188 | "http://eth2.cipig.net:8555", 189 | "http://eth3.cipig.net:8555"], 190 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 191 | "bot_sell": False, 192 | "bot_buy": False 193 | }, 194 | "DASH":{ 195 | "api-id": "dash", 196 | "min_swap": 0.01, 197 | "activate_with":"electrum", 198 | "tx_explorer":"https://explorer.dash.org/tx", 199 | "electrum": [{"url":"electrum1.cipig.net:10061"}, 200 | {"url":"electrum2.cipig.net:10061"}, 201 | {"url":"electrum3.cipig.net:10061"}], 202 | "reserve_balance":0.5, 203 | "premium":1.03, 204 | "min_swap":0.01, 205 | "minQty":"0.00100000", 206 | "maxQty":"900000.00000000", 207 | "stepSize":"0.00100000", 208 | "bot_sell": True, 209 | "bot_buy": True 210 | }, 211 | "DEX":{ 212 | "min_swap": 0.1, 213 | "api-id": "", 214 | "activate_with":"electrum", 215 | "reserve_balance":2, 216 | "override_KMD_buy_price":10, 217 | "tx_explorer":"https://dex.explorer.dexstats.info/tx", 218 | "electrum": [{"url":"electrum1.cipig.net:10006"}, 219 | {"url":"electrum2.cipig.net:10006"}, 220 | {"url":"electrum3.cipig.net:10006"}], 221 | "bot_sell": False, 222 | "bot_buy": True 223 | }, 224 | "DGB":{ 225 | "api-id": "digibyte", 226 | "min_swap": 10, 227 | "activate_with":"electrum", 228 | "reserve_balance":2, 229 | "tx_explorer":"https://digiexplorer.info/tx", 230 | "electrum": [{"url":"electrum1.cipig.net:10059"}, 231 | {"url":"electrum2.cipig.net:10059"}, 232 | {"url":"electrum3.cipig.net:10059"}], 233 | "bot_sell": False, 234 | "bot_buy": False 235 | }, 236 | "DOGE":{ 237 | "api-id": "dogecoin", 238 | "activate_with":"electrum", 239 | "tx_explorer":"https://live.blockcypher.com/doge/tx", 240 | "min_swap": 10, 241 | "electrum": [{"url":"electrum1.cipig.net:10060"}, 242 | {"url":"electrum2.cipig.net:10060"}, 243 | {"url":"electrum3.cipig.net:10060"}], 244 | "reserve_balance":10000, 245 | "premium":1.0377, 246 | "min_swap":10, 247 | "minQty":"1.00000000", 248 | "maxQty":"90000000.00000000", 249 | "stepSize":"1.00000000", 250 | "bot_sell": True, 251 | "bot_buy": True 252 | }, 253 | "ECA":{ 254 | "min_swap": 1, 255 | "activate_with":"electrum", 256 | "reserve_balance":2, 257 | "tx_explorer":"https://explorer.electraproject.org/tx", 258 | "electrum": [{"url":"electrum1.cipig.net:10052"}, 259 | {"url":"electrum2.cipig.net:10052"}, 260 | {"url":"electrum3.cipig.net:10052"}], 261 | "bot_sell": False, 262 | "bot_buy": False 263 | }, 264 | "EMC2":{ 265 | "min_swap": 1, 266 | "activate_with":"electrum", 267 | "reserve_balance":2, 268 | "tx_explorer":"", 269 | "electrum": [{"url":"electrum1.cipig.net:10062"}, 270 | {"url":"electrum2.cipig.net:10062"}, 271 | {"url":"electrum3.cipig.net:10062"}], 272 | "bot_sell": False, 273 | "bot_buy": False 274 | }, 275 | "ETH":{ 276 | "api-id": "ethereum", 277 | "activate_with":"electrum", 278 | "min_swap": 0.01, 279 | "tx_explorer":"https://etherscan.io/tx", 280 | "electrum": ["http://eth1.cipig.net:8555", 281 | "http://eth2.cipig.net:8555", 282 | "http://eth3.cipig.net:8555"], 283 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 284 | "reserve_balance":1, 285 | "premium":1.03, 286 | "min_swap":0.01, 287 | "minQty":"0.001000000", 288 | "maxQty":"100000.00000000", 289 | "stepSize":"0.001000000", 290 | "bot_sell": True, 291 | "bot_buy": True 292 | }, 293 | "FIRO":{ 294 | "min_swap": 1, 295 | "activate_with":"electrum", 296 | "reserve_balance":2, 297 | "tx_explorer":"", 298 | "electrum": [{"url":"electrumx.zcoin.io:50001"}, 299 | {"url":"electrumx01.zcoin.io:50001"}, 300 | {"url":"electrumx02.zcoin.io:50001"}], 301 | "bot_sell": False, 302 | "bot_buy": False 303 | }, 304 | "FTC":{ 305 | "min_swap": 1, 306 | "activate_with":"electrum", 307 | "reserve_balance":2, 308 | "tx_explorer":"https://fsight.chain.tips/tx", 309 | "electrum": [{"url":"electrum1.cipig.net:10054"}, 310 | {"url":"electrum2.cipig.net:10054"}, 311 | {"url":"electrum3.cipig.net:10054"}], 312 | "bot_sell": False, 313 | "bot_buy": False 314 | }, 315 | "GLEEC":{ 316 | "min_swap": 1, 317 | "api-id": "hush", 318 | "activate_with":"electrum", 319 | "reserve_balance":2, 320 | "tx_explorer":"", 321 | "electrum": [{"url":"electrum1.cipig.net:10069"}, 322 | {"url":"electrum2.cipig.net:10069"}, 323 | {"url":"electrum3.cipig.net:10069"}], 324 | "bot_sell": False, 325 | "bot_buy": False 326 | }, 327 | "HODL":{ 328 | "min_swap": 0.1, 329 | "api-id": "", 330 | "activate_with":"electrum", 331 | "reserve_balance":2, 332 | "override_KMD_buy_price":10, 333 | "tx_explorer":"https://hodl.explorer.dexstats.info/tx", 334 | "electrum": [{"url":"electrum1.cipig.net:10009"}, 335 | {"url":"electrum2.cipig.net:10009"}, 336 | {"url":"electrum3.cipig.net:10009"}], 337 | "bot_sell": False, 338 | "bot_buy": True 339 | }, 340 | "ILN":{ 341 | "min_swap": 0.1, 342 | "api-id": "", 343 | "activate_with":"electrum", 344 | "reserve_balance":2, 345 | "override_KMD_buy_price":10, 346 | "tx_explorer":"https://iln.explorer.dexstats.info/tx", 347 | "electrum": [{"url":"electrum1.ilien.io:65011"}, 348 | {"url":"electrum2.ilien.io:65011"}], 349 | "bot_sell": False, 350 | "bot_buy": True 351 | }, 352 | "JUMBLR":{ 353 | "min_swap": 0.1, 354 | "api-id": "", 355 | "activate_with":"electrum", 356 | "reserve_balance":2, 357 | "override_KMD_buy_price":10, 358 | "tx_explorer":"https://jumblr.explorer.dexstats.info/tx", 359 | "electrum": [{"url":"electrum1.cipig.net:10004"}, 360 | {"url":"electrum2.cipig.net:10004"}, 361 | {"url":"electrum3.cipig.net:10004"}], 362 | "bot_sell": False, 363 | "bot_buy": True 364 | }, 365 | "KMD":{ 366 | "api-id": "komodo", 367 | "activate_with":"electrum", 368 | "tx_explorer":"https://www.kmdexplorer.io/tx", 369 | "electrum": [{"url":"electrum1.cipig.net:10001"}, 370 | {"url":"electrum2.cipig.net:10001"}, 371 | {"url":"electrum3.cipig.net:10001"}], 372 | "reserve_balance":100, 373 | "premium":1.03, 374 | "min_swap":0.1, 375 | "minQty":"0.01000000", 376 | "maxQty":"90000000.00000000", 377 | "stepSize":"0.01000000", 378 | "bot_sell": True, 379 | "bot_buy": True 380 | }, 381 | "KOIN":{ 382 | "api-id": "", 383 | "activate_with":"electrum", 384 | "tx_explorer":"https://koin.kmdexplorer.io/tx", 385 | "electrum": [{"url":"electrum1.cipig.net:10024"}, 386 | {"url":"electrum2.cipig.net:10024"}, 387 | {"url":"electrum3.cipig.net:10024"}], 388 | "reserve_balance":1, 389 | "min_swap":0.1, 390 | "bot_sell": True, 391 | "bot_buy": True 392 | }, 393 | "KMDICE":{ 394 | "min_swap": 0.1, 395 | "api-id": "", 396 | "activate_with":"electrum", 397 | "reserve_balance":2, 398 | "tx_explorer":"https://kmdice.explorer.dexstats.info/tx", 399 | "electrum": [{"url":"electrum1.cipig.net:10031"}, 400 | {"url":"electrum2.cipig.net:10031"}, 401 | {"url":"electrum3.cipig.net:10031"}], 402 | "bot_sell": False, 403 | "bot_buy": False 404 | }, 405 | "LABS":{ 406 | "min_swap": 5, 407 | "use_api":"graviex", 408 | "activate_with":"electrum", 409 | "reserve_balance":50000, 410 | "override_KMD_buy_price":0.015, 411 | "override_KMD_sell_price":0.03, 412 | "premium":1.05, 413 | "tx_explorer":"https://labs.explorer.dexstats.info/tx", 414 | "electrum": [{"url":"electrum1.cipig.net:10019"}, 415 | {"url":"electrum2.cipig.net:10019"}, 416 | {"url":"electrum3.cipig.net:10019"}], 417 | "bot_sell": True, 418 | "bot_buy": True 419 | }, 420 | "LINK":{ 421 | "min_swap": 0.5, 422 | "activate_with":"electrum", 423 | "api-id": "chainlink", 424 | "tx_explorer":"https://etherscan.io/tx", 425 | "electrum": ["http://eth1.cipig.net:8555", 426 | "http://eth2.cipig.net:8555", 427 | "http://eth3.cipig.net:8555"], 428 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 429 | "reserve_balance":20, 430 | "premium":1.03, 431 | "minQty":"1.00000000", 432 | "maxQty":"90000000.00000000", 433 | "stepSize":"1.00000000", 434 | "bot_sell": True, 435 | "bot_buy": True 436 | }, 437 | "LTC":{ 438 | "api-id": "litecoin", 439 | "min_swap": 0.01, 440 | "activate_with":"electrum", 441 | "tx_explorer":"https://live.blockcypher.com/ltc/tx", 442 | "electrum": [{"url":"electrum-ltc.bysh.me:50001"}, 443 | {"url":"electrum.ltc.xurious.com:50001"}, 444 | {"url":"ltc.rentonisk.com:50001"}, 445 | {"url":"backup.electrum-ltc.org:50001"}], 446 | "reserve_balance":1, 447 | "premium":1.03, 448 | "min_swap":0.01, 449 | "minQty":"0.01000000", 450 | "maxQty":"100000.00000000", 451 | "stepSize":"0.01000000", 452 | "bot_sell": True, 453 | "bot_buy": True 454 | }, 455 | "MORTY":{ 456 | "min_swap": 0.1, 457 | "api-id": "", 458 | "reserve_balance":2, 459 | "activate_with":"electrum", 460 | "tx_explorer":"https://morty.explorer.dexstats.info/tx", 461 | "electrum": [{"url":"electrum1.cipig.net:10018"}, 462 | {"url":"electrum2.cipig.net:10018"}, 463 | {"url":"electrum3.cipig.net:10018"}], 464 | "bot_sell": True, 465 | "bot_buy": True 466 | }, 467 | "MCL":{ 468 | "min_swap": 0.1, 469 | "api-id": "", 470 | "reserve_balance":2, 471 | "activate_with":"electrum", 472 | "tx_explorer":"https://mcl.explorer.dexstats.info/tx", 473 | "electrum": [{"url":"electrum1.cipig.net:10023"}, 474 | {"url":"electrum2.cipig.net:10023"}, 475 | {"url":"electrum3.cipig.net:10023"}], 476 | "bot_sell": True, 477 | "bot_buy": True 478 | }, 479 | "MGW":{ 480 | "min_swap": 0.1, 481 | "api-id": "", 482 | "reserve_balance":2, 483 | "activate_with":"electrum", 484 | "tx_explorer":"https://mgw.explorer.dexstats.info/tx", 485 | "electrum": [{"url":"electrum1.cipig.net:10015"}, 486 | {"url":"electrum2.cipig.net:10015"}, 487 | {"url":"electrum3.cipig.net:10015"}], 488 | "bot_sell": True, 489 | "bot_buy": True 490 | }, 491 | "MSHARK":{ 492 | "min_swap": 0.1, 493 | "api-id": "", 494 | "reserve_balance":2, 495 | "activate_with":"electrum", 496 | "tx_explorer":"https://mshark.explorer.dexstats.info/tx", 497 | "electrum": [{"url":"electrum1.cipig.net:10013"}, 498 | {"url":"electrum2.cipig.net:10013"}, 499 | {"url":"electrum3.cipig.net:10013"}], 500 | "bot_sell": True, 501 | "bot_buy": True 502 | }, 503 | "MONA":{ 504 | "min_swap": 0.1, 505 | "api-id": "", 506 | "reserve_balance":2, 507 | "activate_with":"electrum", 508 | "tx_explorer":"", 509 | "electrum": [{"url":"electrumx3.monacoin.nl:50001"}, 510 | {"url":"electrumx1.monacoin.ninja:50001"}, 511 | {"url":"electrumx.tamami-foundation.org:50001"}], 512 | "bot_sell": True, 513 | "bot_buy": True 514 | }, 515 | "NAV":{ 516 | "min_swap": 0.1, 517 | "api-id": "", 518 | "reserve_balance":2, 519 | "activate_with":"electrum", 520 | "tx_explorer":"", 521 | "electrum": [{"url":"electrum1.cipig.net:10056"}, 522 | {"url":"electrum2.cipig.net:10056"}, 523 | {"url":"electrum3.cipig.net:10056"}], 524 | "bot_sell": True, 525 | "bot_buy": True 526 | }, 527 | "NMC":{ 528 | "min_swap": 0.1, 529 | "api-id": "", 530 | "reserve_balance":2, 531 | "activate_with":"electrum", 532 | "tx_explorer":"", 533 | "electrum": [{"url":"electrumx1.nmc.bitclc.net:50001"}, 534 | {"url":"electrumx2.nmc.bitclc.net:50001"}, 535 | {"url":"electrumx3.nmc.bitclc.net:50001"}], 536 | "bot_sell": True, 537 | "bot_buy": True 538 | }, 539 | "OOT":{ 540 | "min_swap": 1, 541 | "api-id": "", 542 | "reserve_balance":2, 543 | "activate_with":"electrum", 544 | "tx_explorer":"https://oot.explorer.dexstats.info/tx", 545 | "electrum": [{"url":"electrum1.utrum.io:10088"}, 546 | {"url":"electrum2.utrum.io:10088"}], 547 | "bot_sell": False, 548 | "bot_buy": False 549 | }, 550 | "PAX":{ 551 | "min_swap": 0.5, 552 | "api-id": "paxos-standard", 553 | "activate_with":"electrum", 554 | "reserve_balance":2, 555 | "tx_explorer":"https://etherscan.io/tx", 556 | "electrum": ["http://eth1.cipig.net:8555", 557 | "http://eth2.cipig.net:8555", 558 | "http://eth3.cipig.net:8555"], 559 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 560 | "bot_sell": False, 561 | "bot_buy": False 562 | }, 563 | "RFOX":{ 564 | "min_swap": 0.5, 565 | "api-id": "paxos-standard", 566 | "activate_with":"electrum", 567 | "reserve_balance":2, 568 | "tx_explorer":"https://etherscan.io/tx", 569 | "electrum": ["http://eth1.cipig.net:8555", 570 | "http://eth2.cipig.net:8555", 571 | "http://eth3.cipig.net:8555"], 572 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 573 | "bot_sell": False, 574 | "bot_buy": False 575 | }, 576 | "PBC":{ 577 | "min_swap": 1, 578 | "api-id": "", 579 | "reserve_balance":2, 580 | "activate_with":"electrum", 581 | "tx_explorer":"https://oot.explorer.dexstats.info/tx", 582 | "electrum": [{"url":"electrum1.cipig.net:10070"}, 583 | {"url":"electrum2.cipig.net:10070"}, 584 | {"url":"electrum3.cipig.net:10070"}], 585 | "bot_sell": False, 586 | "bot_buy": False 587 | }, 588 | "PANGEA":{ 589 | "min_swap": 1, 590 | "api-id": "", 591 | "reserve_balance":2, 592 | "activate_with":"electrum", 593 | "tx_explorer":"https://pangea.explorer.dexstats.info/tx", 594 | "electrum": [{"url":"electrum1.cipig.net:10010"}, 595 | {"url":"electrum2.cipig.net:10010"}, 596 | {"url":"electrum3.cipig.net:10010"}], 597 | "bot_sell": False, 598 | "bot_buy": False 599 | }, 600 | "QTUM":{ 601 | "min_swap": 4, 602 | "activate_with":"electrum", 603 | "tx_explorer":"https://qtum.info/tx", 604 | "electrum": [{"url":"s1.qtum.info:50001"}, 605 | {"url":"s2.qtum.info:50001"}, 606 | {"url":"s3.qtum.info:50001"}, 607 | {"url":"s4.qtum.info:50001"}, 608 | {"url":"s5.qtum.info:50001"}, 609 | {"url":"s6.qtum.info:50001"}, 610 | {"url":"s7.qtum.info:50001"}, 611 | {"url":"s8.qtum.info:50001"}, 612 | {"url":"s9.qtum.info:50001"} 613 | ], 614 | "reserve_balance":50, 615 | "premium":1.03, 616 | "min_swap":3.4, 617 | "minQty":"0.01000000", 618 | "maxQty":"10000000.00000000", 619 | "stepSize":"0.01000000", 620 | "bot_sell": True, 621 | "bot_buy": True 622 | }, 623 | "REVS":{ 624 | "min_swap": 0.1, 625 | "api-id": "", 626 | "activate_with":"electrum", 627 | "reserve_balance":2, 628 | "tx_explorer":"https://revs.explorer.dexstats.info/tx", 629 | "electrum": [{"url":"electrum1.cipig.net:10003"}, 630 | {"url":"electrum2.cipig.net:10003"}, 631 | {"url":"electrum3.cipig.net:10003"}], 632 | "bot_sell": False, 633 | "bot_buy": False 634 | }, 635 | "RVN":{ 636 | "min_swap": 5, 637 | "activate_with":"electrum", 638 | "tx_explorer":"https://ravencoin.network/tx", 639 | "electrum": [{"url":"electrum1.cipig.net:10051"}, 640 | {"url":"electrum2.cipig.net:10051"}, 641 | {"url":"electrum3.cipig.net:10051"}], 642 | "reserve_balance":2500, 643 | "premium":1.03, 644 | "min_swap":1, 645 | "minQty":"1.00000000", 646 | "maxQty":"90000000.00000000", 647 | "stepSize":"1.00000000", 648 | "bot_sell": True, 649 | "bot_buy": True 650 | }, 651 | "SPACE":{ 652 | "min_swap": 5, 653 | "activate_with":"electrum", 654 | "reserve_balance":2, 655 | "tx_explorer":"https://space.explorer.dexstats.info/tx", 656 | "electrum": [{"url":"electrum1.spaceworks.co:50001"}, 657 | {"url":"electrum2.spaceworks.co:50001"}, 658 | {"url":"electrum1.spaceworks.co:50002"}], 659 | "bot_sell": False, 660 | "bot_buy": False 661 | }, 662 | "RICK":{ 663 | "min_swap": 0.1, 664 | "api-id": "", 665 | "activate_with":"electrum", 666 | "reserve_balance":2, 667 | "tx_explorer":"https://rick.explorer.dexstats.info/tx", 668 | "electrum": [{"url":"electrum1.cipig.net:10017"}, 669 | {"url":"electrum2.cipig.net:10017"}, 670 | {"url":"electrum3.cipig.net:10017"}], 671 | "bot_sell": True, 672 | "bot_buy": True 673 | }, 674 | "SUPERNET":{ 675 | "min_swap": 0.1, 676 | "api-id": "", 677 | "activate_with":"electrum", 678 | "reserve_balance":2, 679 | "tx_explorer":"https://supernet.explorer.dexstats.info/tx", 680 | "electrum": [{"url":"electrum1.cipig.net:10005"}, 681 | {"url":"electrum2.cipig.net:10005"}, 682 | {"url":"electrum3.cipig.net:10005"}], 683 | "bot_sell": False, 684 | "bot_buy": False 685 | }, 686 | "THC":{ 687 | "min_swap": 5, 688 | "activate_with":"electrum", 689 | "reserve_balance":2, 690 | "tx_explorer":"https://thc.explorer.dexstats.info/tx", 691 | "electrum": [{"url":"165.22.52.123:10022"}, 692 | {"url":"157.230.45.184:10022"}], 693 | "bot_sell": False, 694 | "bot_buy": False 695 | }, 696 | "USDC":{ 697 | "min_swap": 0.5, 698 | "api-id": "usd-coin", 699 | "activate_with":"electrum", 700 | "reserve_balance":2, 701 | "tx_explorer":"https://etherscan.io/tx", 702 | "electrum": ["http://eth1.cipig.net:8555", 703 | "http://eth2.cipig.net:8555", 704 | "http://eth3.cipig.net:8555"], 705 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 706 | "bot_sell": False, 707 | "bot_buy": False 708 | }, 709 | "TUSD":{ 710 | "min_swap": 0.5, 711 | "api-id": "true-usd", 712 | "activate_with":"electrum", 713 | "reserve_balance":2, 714 | "tx_explorer":"https://etherscan.io/tx", 715 | "electrum": ["http://eth1.cipig.net:8555", 716 | "http://eth2.cipig.net:8555", 717 | "http://eth3.cipig.net:8555"], 718 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 719 | "bot_sell": False, 720 | "bot_buy": False 721 | }, 722 | "USDC":{ 723 | "min_swap": 0.5, 724 | "api-id": "true-usd", 725 | "activate_with":"electrum", 726 | "reserve_balance":2, 727 | "tx_explorer":"https://etherscan.io/tx", 728 | "electrum": ["http://eth1.cipig.net:8555", 729 | "http://eth2.cipig.net:8555", 730 | "http://eth3.cipig.net:8555"], 731 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 732 | "bot_sell": False, 733 | "bot_buy": False 734 | }, 735 | "VRA":{ 736 | "min_swap": 0.5, 737 | "api-id": "true-usd", 738 | "activate_with":"electrum", 739 | "reserve_balance":2, 740 | "tx_explorer":"https://etherscan.io/tx", 741 | "electrum": ["http://eth1.cipig.net:8555", 742 | "http://eth2.cipig.net:8555", 743 | "http://eth3.cipig.net:8555"], 744 | "contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871", 745 | "bot_sell": False, 746 | "bot_buy": False 747 | }, 748 | "VRSC":{ 749 | "min_swap": 0.1, 750 | "activate_with":"electrum", 751 | "reserve_balance":2, 752 | "tx_explorer":"https://explorer.veruscoin.io/tx", 753 | "electrum": [{"url":"el0.veruscoin.io:17485"}, 754 | {"url":"el1.veruscoin.io:17485"}, 755 | {"url":"el2.veruscoin.io:17485"}], 756 | "bot_sell": False, 757 | "bot_buy": False 758 | }, 759 | "WLC":{ 760 | "min_swap": 0.1, 761 | "api-id": "", 762 | "activate_with":"electrum", 763 | "reserve_balance":2, 764 | "tx_explorer":"https://wlc.explorer.dexstats.info/tx", 765 | "electrum": [{"url":"electrum1.cipig.net:10014"}, 766 | {"url":"electrum2.cipig.net:10014"}, 767 | {"url":"electrum3.cipig.net:10014"}], 768 | "bot_sell": False, 769 | "bot_buy": False 770 | }, 771 | "ZEC":{ 772 | "min_swap": 0.1, 773 | "api-id": "zcash", 774 | "activate_with":"electrum", 775 | "reserve_balance":1, 776 | "tx_explorer":"https://explorer.zcha.in/transactions", 777 | "electrum": [{"url":"electrum3.cipig.net:10058"}, 778 | {"url":"electrum3.cipig.net:10058"}, 779 | {"url":"electrum3.cipig.net:10058"}], 780 | "bot_sell": True, 781 | "bot_buy": True 782 | }, 783 | "ZER":{ 784 | "min_swap": 0.1, 785 | "api-id": "zaddex", 786 | "activate_with":"electrum", 787 | "reserve_balance":2, 788 | "tx_explorer":"", 789 | "electrum": [{"url":"electrum1.cipig.net:10065"}, 790 | {"url":"electrum2.cipig.net:10065"}, 791 | {"url":"electrum3.cipig.net:10065"}], 792 | "bot_sell": False, 793 | "bot_buy": False 794 | }, 795 | "ZEXO":{ 796 | "min_swap": 0.1, 797 | "api-id": "zaddex", 798 | "activate_with":"electrum", 799 | "reserve_balance":2, 800 | "tx_explorer":"https://zexo.explorer.dexstats.info/tx", 801 | "electrum": [{"url":"electrum1.cipig.net:10035"}, 802 | {"url":"electrum2.cipig.net:10035"}, 803 | {"url":"electrum3.cipig.net:10035"}], 804 | "bot_sell": False, 805 | "bot_buy": False 806 | }, 807 | "ZILLA":{ 808 | "min_swap": 0.1, 809 | "api-id": "", 810 | "activate_with":"electrum", 811 | "reserve_balance":2, 812 | "tx_explorer":"https://zilla.explorer.dexstats.info/tx", 813 | "electrum": [{"url":"electrum1.cipig.net:10028"}, 814 | {"url":"electrum2.cipig.net:10028"}, 815 | {"url":"electrum3.cipig.net:10028"}], 816 | "bot_sell": False, 817 | "bot_buy": False 818 | } 819 | } 820 | 821 | # Input coins you want to trade here. 822 | # reserve_balance: excess funds will be sent to your Binance wallet 823 | # premium: value relative to binance market rate to setprices as marketmaker. 824 | # min/max/stepsize need to be set from values from 825 | # https://api.binance.com/api/v1/exchangeInfo 826 | 827 | cointags = [] 828 | buy_list = [] 829 | sell_list = [] 830 | for ticker in coins: 831 | cointags.append(ticker) 832 | if coins[ticker]['bot_buy']: 833 | buy_list.append(ticker) 834 | if coins[ticker]['bot_sell']: 835 | sell_list.append(ticker) 836 | trading_list = list(set(buy_list+sell_list)) 837 | -------------------------------------------------------------------------------- /lib/tuilib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | import time 6 | import requests 7 | import subprocess 8 | from os.path import expanduser 9 | from . import coinslib, rpclib, binance_api 10 | from decimal import Decimal, ROUND_DOWN 11 | 12 | import bitcoin 13 | from bitcoin.wallet import P2PKHBitcoinAddress 14 | from bitcoin.core import x 15 | from bitcoin.core import CoreMainParams 16 | 17 | class CoinParams(CoreMainParams): 18 | MESSAGE_START = b'\x24\xe9\x27\x64' 19 | DEFAULT_PORT = 7770 20 | BASE58_PREFIXES = {'PUBKEY_ADDR': 60, 21 | 'SCRIPT_ADDR': 85, 22 | 'SECRET_KEY': 188} 23 | bitcoin.params = CoinParams 24 | 25 | def get_radd_from_pub(pub): 26 | try: 27 | taker_addr = str(P2PKHBitcoinAddress.from_pubkey(x("02"+pub))) 28 | except: 29 | taker_addr = pub 30 | return str(taker_addr) 31 | 32 | cwd = os.getcwd() 33 | script_path = sys.path[0] 34 | home = expanduser("~") 35 | 36 | ignored_addresses = ['RDbAXLCmQ2EN7daEZZp7CC9xzkcN8DfAZd', ''] 37 | 38 | def colorize(string, color): 39 | colors = { 40 | 'black':'\033[30m', 41 | 'red':'\033[31m', 42 | 'green':'\033[32m', 43 | 'orange':'\033[33m', 44 | 'blue':'\033[34m', 45 | 'purple':'\033[35m', 46 | 'cyan':'\033[36m', 47 | 'lightgrey':'\033[37m', 48 | 'darkgrey':'\033[90m', 49 | 'lightred':'\033[91m', 50 | 'lightgreen':'\033[92m', 51 | 'yellow':'\033[93m', 52 | 'lightblue':'\033[94m', 53 | 'pink':'\033[95m', 54 | 'lightcyan':'\033[96m', 55 | } 56 | if color not in colors: 57 | return str(string) 58 | else: 59 | return colors[color] + str(string) + '\033[0m' 60 | 61 | hl = colorize("|", 'lightblue') 62 | 63 | def wait_continue(msg=''): 64 | input(colorize(msg+"Press [Enter] to continue...", 'orange')) 65 | 66 | def create_MM2_json(): 67 | data = {} 68 | rpc_pass = input(colorize("Enter an RPC password: ", 'orange')) 69 | netid = input(colorize("Enter a NET ID (default = 9999): ", 'orange')) 70 | userhome = expanduser("~") 71 | passphrase = input(colorize("Enter a wallet seed: ", 'orange')) 72 | data.update({"gui":"MM2GUI"}) 73 | data.update({"rpc_password":rpc_pass}) 74 | data.update({"netid":int(netid)}) 75 | data.update({"userhome":userhome}) 76 | data.update({"passphrase":passphrase}) 77 | with open('MM2.json', 'w', encoding='utf-8') as f: 78 | json.dump(data, f, ensure_ascii=False, indent=4) 79 | print(colorize("MM2.json file created!", 'green')) 80 | wait_continue() 81 | 82 | 83 | ## MM2 management 84 | def start_mm2(logfile='mm2_output.log'): 85 | if os.path.isfile('mm2'): 86 | mm2_output = open(logfile,'w+') 87 | subprocess.Popen(["./mm2"], stdout=mm2_output, stderr=mm2_output, universal_newlines=True) 88 | msg = "Marketmaker 2 starting. Use 'tail -f "+logfile+"' for mm2 console messages. " 89 | time.sleep(1) 90 | wait_continue(msg) 91 | else: 92 | print(colorize("\nmm2 binary not found in "+script_path+"!", 'red')) 93 | print(colorize("See https://developers.komodoplatform.com/basic-docs/atomicdex/atomicdex-setup/get-started-atomicdex.html for install instructions.", 'orange')) 94 | print(colorize("Exiting...\n", 'blue')) 95 | sys.exit() 96 | 97 | def stop_mm2(node_ip, user_pass): 98 | params = {'userpass': user_pass, 'method': 'stop'} 99 | try: 100 | r = requests.post(node_ip, json=params) 101 | msg = "MM2 stopped. " 102 | except: 103 | msg = "MM2 was not running. " 104 | wait_continue(msg) 105 | 106 | 107 | def exit(node_ip, user_pass): 108 | mm2_active = rpclib.get_status(node_ip, user_pass)[1] 109 | if mm2_active: 110 | while True: 111 | q = input(colorize("Cancel all orders (y/n)? ", 'orange')) 112 | if q == 'n' or q == 'N': 113 | break 114 | elif q == 'y' or q == 'Y': 115 | resp = rpclib.cancel_all(node_ip, user_pass).json() 116 | print(colorize("All orders cancelled!","orange")) 117 | break 118 | else: 119 | print(colorize("Invalid response, must be [Y/y] or [N/n]", 'red')) 120 | pass 121 | while True: 122 | q = input(colorize("Stop Marketmaker 2? (y/n): ", 'orange')) 123 | if q == 'y' or q == 'Y': 124 | stop_mm2(node_ip, user_pass) 125 | print(colorize("Goodbye!", 'blue')) 126 | sys.exit() 127 | elif q == 'n' or q == 'N': 128 | sys.exit() 129 | else: 130 | print(colorize("Invalid response, use [Y/y] or [N/n]...", 'red')) 131 | else: 132 | print(colorize("Goodbye!", 'blue')) 133 | sys.exit() 134 | 135 | def activate_all(node_ip, user_pass): 136 | for coin in coinslib.coins: 137 | if coinslib.coins[coin]['activate_with'] == 'native': 138 | r = rpclib.enable(node_ip, user_pass, coin) 139 | print(r.json()) 140 | print(colorize("Activating "+coin+" in native mode", 'cyan')) 141 | else: 142 | r = rpclib.electrum(node_ip, user_pass, coin) 143 | print(r.json()) 144 | print(colorize("Activating "+coin+" with electrum", 'cyan')) 145 | 146 | def validate_selection(interrogative, selection_list): 147 | while True: 148 | index = int(input(colorize(interrogative, 'orange')))-1 149 | try: 150 | selected = selection_list[index] 151 | return selected 152 | except: 153 | print(colorize("Invalid selection, must be number between 1 and "+str(len(selection_list)), 'red')) 154 | pass 155 | 156 | def select_coin(interrogative, coin_list, ignore=[]): 157 | i = 1 158 | row = '' 159 | coin_list.sort() 160 | for coin in ignore: 161 | coin_list.remove(coin) 162 | for coin in coin_list: 163 | if i < 10: 164 | row += '{:<14}'.format(" ["+str(i)+"] "+coin) 165 | else: 166 | row += '{:<14}'.format("["+str(i)+"] "+coin) 167 | if len(row) > 64 or i == len(coin_list): 168 | print(colorize(row, 'blue')) 169 | row = '' 170 | i += 1 171 | selection = validate_selection(interrogative, coin_list) 172 | return selection 173 | 174 | def pair_orderbook_table(node_ip, user_pass, base, rel, coins_data='', no_stop=False): 175 | if not no_stop: 176 | print(colorize('Getting orderbook...', 'cyan')) 177 | orderbook = rpclib.orderbook(node_ip, user_pass, base, rel).json() 178 | if coins_data == '': 179 | coins_data = rpclib.build_coins_data(node_ip, user_pass, [base,rel]) 180 | try: 181 | balance_data = rpclib.my_balance(node_ip, user_pass, base).json() 182 | addr = balance_data['address'] 183 | except Exception as e: 184 | addr = '' 185 | pass 186 | row = "-"*175 187 | orderbook_trim = [] 188 | header_row = hl+'{:^10}'.format('ORDER NUM')+hl+'{:^14}'.format('PAIR')+hl+'{:^16}'.format('VOLUME'+" ("+base+")")+hl \ 189 | +'{:^18}'.format('PRICE (USD)')+hl \ 190 | +'{:^36}'.format('SELLER ADDRESS')+hl+'{:^18}'.format('PRICE ('+rel+')')+hl \ 191 | +'{:^18}'.format('MM2 RATE')+hl+'{:^18}'.format('MARKET RATE')+hl \ 192 | +'{:^16}'.format('DIFFERENTIAL')+hl 193 | if not no_stop: 194 | print(" "+row) 195 | print(" "+header_row) 196 | print(" "+row) 197 | elif 'asks' in orderbook and len(orderbook['asks']) == 0 and no_stop: 198 | return [] 199 | try: 200 | market_rate = coins_data[base]['BTC_price']/coins_data[rel]['BTC_price'] 201 | except: 202 | market_rate = 0 203 | pair = rel+"/"+base 204 | try: 205 | btc_price = coins_data[base]['BTC_price'] 206 | aud_price = coins_data[base]['AUD_price'] 207 | usd_price = coins_data[base]['USD_price'] 208 | price_source = coins_data[base]['price_source'] 209 | if 'asks' in orderbook and len(orderbook['asks']) > 0: 210 | i = 1 211 | for bid in orderbook['asks']: 212 | if bid['address'] != addr: 213 | orderbook_trim.append(bid) 214 | bid['price'] = str(round(float(bid['price']),7)) 215 | price = str(bid['price']) 216 | volume = str(bid['maxvolume']) 217 | if market_rate != 0: 218 | differential = float(price)/float(market_rate)-1 219 | else: 220 | differential = 0 221 | diff_pct = str(differential*100)[:5]+"%" 222 | if differential < 0: 223 | diff_pct = colorize('{:^16}'.format(str(diff_pct)[:8]), 'green') 224 | elif differential > 0.07: 225 | diff_pct = colorize('{:^16}'.format(str(diff_pct)[:8]), 'red') 226 | else: 227 | diff_pct = colorize('{:^16}'.format(str(diff_pct)[:8]), 'default') 228 | rel_price = float(price) 229 | if not no_stop: 230 | print(" "+hl+'{:^10}'.format("["+str(i)+"]")+hl+'{:^14}'.format(pair)+hl+'{:^16}'.format(volume[:10])+hl \ 231 | +'{:^18}'.format("$"+str(usd_price)[:14])+hl \ 232 | +'{:^36}'.format(str(bid['address']))+hl+'{:^18}'.format(str(rel_price)[:14])+hl \ 233 | +'{:^18}'.format(str(price)[:14])+hl+'{:^18}'.format(str(market_rate)[:14])+hl \ 234 | +str(diff_pct)+"\033[0m"+hl \ 235 | ) 236 | i += 1 237 | print(" "+row) 238 | elif differential < 0: 239 | print(" "+row) 240 | print(" "+header_row) 241 | print(" "+row) 242 | print(" "+hl+'{:^10}'.format("["+str(i)+"]")+hl+'{:^14}'.format(pair)+hl+'{:^16}'.format(volume[:10])+hl \ 243 | +'{:^18}'.format("$"+str(usd_price)[:14])+hl \ 244 | +'{:^36}'.format(str(bid['address']))+hl+'{:^18}'.format(str(rel_price)[:14])+hl \ 245 | +'{:^18}'.format(str(price)[:14])+hl+'{:^18}'.format(str(market_rate)[:14])+hl \ 246 | +str(diff_pct)+"\033[0m"+hl \ 247 | ) 248 | i += 1 249 | print(" "+row) 250 | else: 251 | orderbook_trim.append(bid) 252 | price = str(bid['price']) 253 | volume = str(bid['maxvolume']) 254 | if market_rate != 0: 255 | differential = float(price)/float(market_rate)-1 256 | else: 257 | differential = 0 258 | diff_pct = str(differential*100)[:5]+"%" 259 | if differential < 0: 260 | diff_pct = colorize('{:^16}'.format(str(diff_pct)[:8]), 'green') 261 | elif differential > 0.07: 262 | diff_pct = colorize('{:^16}'.format(str(diff_pct)[:8]), 'red') 263 | else: 264 | diff_pct = colorize('{:^16}'.format(str(diff_pct)[:8]), 'default') 265 | rel_price = float(price) 266 | print(" "+hl+'{:^10}'.format("["+str(i)+"]")+hl+'{:^14}'.format(pair)+hl+'{:^16}'.format(volume[:10])+hl \ 267 | +'{:^18}'.format("$"+str(usd_price)[:14])+hl \ 268 | +colorize('{:^36}'.format("YOUR ORDER"), 'cyan')+hl+'{:^18}'.format(str(rel_price)[:14])+hl \ 269 | +'{:^18}'.format(str(price)[:14])+hl+'{:^18}'.format(str(market_rate)[:14])+hl \ 270 | +str(diff_pct)+"\033[0m"+hl \ 271 | ) 272 | i += 1 273 | print(" "+row) 274 | 275 | elif not no_stop: 276 | print(" "+hl+'{:^10}'.format("[*]")+hl+'{:^14}'.format(pair)+hl+'{:^16}'.format("-")+hl \ 277 | +'{:^18}'.format("$"+str(usd_price)[:14])+hl \ 278 | +'{:^36}'.format(str("-"))+hl+'{:^18}'.format(str(market_rate)[:14])+hl \ 279 | +'{:^18}'.format(str('-'))+hl+'{:^18}'.format(str(market_rate)[:14])+hl \ 280 | +'{:^18}'.format(str('-')+"\033[0m")+hl \ 281 | ) 282 | print(" "+row) 283 | while True: 284 | q = input(colorize("No orders in orderbook for "+base+"/"+rel+"! Create one manually? (y/n): ", 'orange')) 285 | if q == 'N' or q == 'n': 286 | return 'back to menu' 287 | elif q == 'Y' or q == 'y': 288 | while True: 289 | outcome = create_buy(node_ip, user_pass, base, rel) 290 | if outcome == 'back to menu': 291 | break 292 | break 293 | else: 294 | print(colorize("Enter [Y/y] or [N/n] only, try again... ", 'red')) 295 | except Exception as e: 296 | print("Orderbook error: "+str(e)) 297 | pass 298 | return orderbook_trim 299 | 300 | def rat_float(rat): 301 | numerator = rat[0] 302 | denominator = rat[1] 303 | if numerator[0] == -1: 304 | numerator = numerator[1]*-1 305 | else: 306 | numerator = numerator[1] 307 | print(numerator) 308 | if denominator[0] == -1: 309 | denominator = denominator[1]*-1 310 | else: 311 | denominator = denominator[1] 312 | print(denominator) 313 | fee = numerator[0]/denominator[0] 314 | return fee 315 | 316 | 317 | def show_orderbook_pair(node_ip, user_pass, base='', rel=''): 318 | coin_status = rpclib.check_coins_status(node_ip, user_pass) 319 | active_coins = coin_status[3] 320 | if base == '': 321 | base = select_coin("Select coin to buy: ", active_coins) 322 | if rel == '': 323 | rel = select_coin("Select coin to sell: ", active_coins, [base]) 324 | # todo: ignore orders set by user 325 | try: 326 | orderbook = pair_orderbook_table(node_ip, user_pass, base, rel) 327 | while True: 328 | if orderbook == 'back to menu': 329 | return 'back to menu' 330 | bal = rpclib.my_balance(node_ip, user_pass, rel).json()['balance'] 331 | print(colorize("Your "+rel+" balance: "+str(bal), 'green')) 332 | q = input(colorize("Select an order number to start a trade, [R]efresh orderbook, [C]reate one manually, or [E]xit to menu: ", 'orange')) 333 | if q == 'e' or q == 'E': 334 | break 335 | if q == 'R' or q == 'r': 336 | orderbook = pair_orderbook_table(node_ip, user_pass, base, rel) 337 | elif q == 'c' or q == 'C': 338 | while True: 339 | outcome = create_buy(node_ip, user_pass, base, rel) 340 | if outcome == 'back to menu': 341 | break 342 | break 343 | else: 344 | try: 345 | selected = orderbook[int(q)-1] 346 | price = float(selected['price']) 347 | while True: 348 | try: 349 | fee = rpclib.get_fee(node_ip, user_pass, rel).json()['result']['amount'] 350 | print(fee) 351 | fee = rat_float(fee) 352 | print(fee) 353 | print(bal) 354 | max_afforded_val = ((float(bal)-float(fee)*2)/price)*0.99 355 | volume = float(selected['maxvolume']) 356 | if volume > max_afforded_val: 357 | max_vol = max_afforded_val 358 | else: 359 | max_vol = volume 360 | max_vol = Decimal(Decimal(max_vol).quantize(Decimal('.001'), rounding=ROUND_DOWN)) 361 | buy_num = float(input(colorize("How many "+base+" to buy at "+str(price)+"? (max. "+str(max_vol)+"): ", 'orange'))) 362 | if buy_num > max_afforded_val: 363 | print(colorize("Can't buy more than max affordable volume! Try again..." , 'red')) 364 | else: 365 | while True: 366 | q = input(colorize("Confirm buy order, "+str(buy_num)+" "+base+" for "+str(price*buy_num)+" "+rel+" (y/n): ",'orange')) 367 | if q == 'Y' or q == 'y': 368 | resp = rpclib.buy(node_ip, user_pass, base, rel, buy_num, price).json() 369 | if 'error' not in resp: 370 | input(colorize("Order submitted! Press [Enter] to continue...", 'green')) 371 | else: 372 | print(resp) 373 | input(colorize("Press [Enter] to continue...", 'orange')) 374 | break 375 | elif q == 'N' or q == 'n': 376 | break 377 | else: 378 | print(colorize("Invalid selection, must be [Y/y] or [N/n]. Try again...", 'red')) 379 | break 380 | except Exception as e: 381 | print(e) 382 | pass 383 | break 384 | except Exception as e: 385 | print(e) 386 | print(colorize("Invalid selection, must be [E/e] or a number between 1 and "+str(len(orderbook)), 'red')) 387 | pass 388 | except Exception as e: 389 | print("Orderbooks error: "+str(e)) 390 | pass 391 | wait_continue() 392 | 393 | def create_buy(node_ip, user_pass, base, rel): 394 | try: 395 | rel_bal = rpclib.my_balance(node_ip, user_pass, rel).json()['balance'] 396 | rel_price = float(input(colorize("What "+rel+" price?: ", 'orange'))) 397 | rel_price = round(rel_price,7) 398 | max_vol = float(rel_bal)/rel_price 399 | max_vol = round(max_vol,7) 400 | buy_num = float(input(colorize("How many "+base+" to buy? (max. "+'{:^8}'.format(str(max_vol))+"): ", 'orange'))) 401 | buy_num = round(buy_num,7) 402 | if buy_num > max_vol: 403 | print(colorize("Can't buy more than max volume! Try again..." , 'red')) 404 | else: 405 | while True: 406 | q = input(colorize("Confirm buy order, "+str(buy_num)+" "+base+" for "+str(float(rel_price)*buy_num)+" "+rel+" (y/n): ",'orange')) 407 | if q == 'Y' or q == 'y': 408 | resp = rpclib.buy(node_ip, user_pass, base, rel, buy_num, rel_price).json() 409 | if 'error' in resp: 410 | print(colorize("Buy Error: "+str(resp), 'red')) 411 | else: 412 | print(colorize("Order submitted!", 'green')) 413 | wait_continue() 414 | return 'back to menu' 415 | elif q == 'N' or q == 'n': 416 | return 'back to menu' 417 | else: 418 | print(colorize("Invalid selection, must be [Y/y] or [N/n]. Try again...", 'red')) 419 | except Exception as e: 420 | print("Create buy error: "+str(e)) 421 | wait_continue() 422 | return 'back to menu' 423 | 424 | 425 | def show_orders_table(node_ip, user_pass, coins_data='', bot=False): 426 | my_current_orders = rpclib.my_orders(node_ip, user_pass).json()['result'] 427 | num_orders = len(my_current_orders['maker_orders']) + len(my_current_orders['taker_orders']) 428 | if num_orders == 0: 429 | print(colorize("You have no pending orders!", 'red')) 430 | if not bot: 431 | wait_continue() 432 | return 'back to menu' 433 | if coins_data == '': 434 | coins_data = rpclib.build_coins_data(node_ip, user_pass) 435 | total_btc_val = 0 436 | my_order_list = [] 437 | try: 438 | row = colorize("-"*174, 'blue') 439 | print(" "+row) 440 | print( 441 | hl+'{:^11}'.format("ORDER NUM")+hl+'{:^14}'.format('ORDER TYPE')+hl \ 442 | +'{:^14}'.format('PAIR')+hl+'{:^18}'.format('VOLUME')+hl \ 443 | +'{:^18}'.format('PRICE (USD)')+hl+'{:^18}'.format('PRICE (AUD)')+hl \ 444 | +'{:^18}'.format('PRICE (BTC)')+hl+'{:^18}'.format('MY PRICE')+hl \ 445 | +'{:^18}'.format('MARKET RATE')+hl \ 446 | +'{:^16}'.format('DIFFERENTIAL')+hl \ 447 | ) 448 | print(" "+row) 449 | i = 1 450 | for order in my_current_orders['maker_orders']: 451 | my_order_list.append(my_current_orders['maker_orders'][order]) 452 | order_type = "MAKER" 453 | base = my_current_orders['maker_orders'][order]['base'] 454 | rel = my_current_orders['maker_orders'][order]['rel'] 455 | price = my_current_orders['maker_orders'][order]['price'] 456 | volume = my_current_orders['maker_orders'][order]['available_amount'] 457 | try: 458 | market_rate = coins_data[base]['BTC_price']/coins_data[rel]['BTC_price'] 459 | except: 460 | market_rate = 0 461 | pair = rel+"/"+base 462 | btc_price = coins_data[rel]['BTC_price'] 463 | aud_price = coins_data[rel]['AUD_price'] 464 | usd_price = coins_data[rel]['USD_price'] 465 | if market_rate != 0: 466 | differential = float(price)/float(market_rate)-1 467 | else: 468 | differential = 0 469 | diff_pct = str(differential*100)[:5]+"%" 470 | if differential < 0: 471 | differential = colorize('{:^16}'.format(str(diff_pct)[:8]), 'green') 472 | elif differential > 0.07: 473 | differential = colorize('{:^16}'.format(str(diff_pct)[:8]), 'red') 474 | else: 475 | differential = colorize('{:^16}'.format(str(diff_pct)[:8]), 'default') 476 | price_source = coins_data[base]['price_source'] 477 | btc_price = str(btc_price) 478 | if price_source == 'binance': 479 | btc_price = colorize('{:^18}'.format(str(btc_price)), 'yellow') 480 | elif price_source == 'coingecko': 481 | btc_price = colorize('{:^18}'.format(str(btc_price)), 'pink') 482 | elif price_source == 'mm2_orderbook': 483 | btc_price = colorize('{:^18}'.format(str(btc_price)), 'cyan') 484 | #print(price_source) 485 | #print(btc_price) 486 | rel_price = float(price) 487 | print(colorize(" "+hl+'{:^11}'.format("["+str(i)+"]")+hl+'{:^14}'.format(order_type)+hl \ 488 | +'{:^14}'.format(pair)+hl+'{:^18}'.format(volume[:14])+hl \ 489 | +'{:^18}'.format("$"+str(usd_price)[:14])+hl+'{:^18}'.format("$"+str(aud_price)[:14])+hl \ 490 | +'{:^18}'.format(str(btc_price))+hl+'{:^18}'.format(str(rel_price)[:14])+hl \ 491 | +'{:^18}'.format(str(market_rate)[:14])+hl \ 492 | +str(differential)+hl, 'blue') \ 493 | ) 494 | i += 1 495 | print(" "+row) 496 | for order in my_current_orders['taker_orders']: 497 | my_order_list.append(my_current_orders['taker_orders'][order]) 498 | order_type = "TAKER" 499 | base = my_current_orders['taker_orders'][order]['request']['base'] 500 | rel = my_current_orders['taker_orders'][order]['request']['rel'] 501 | base_amount = my_current_orders['taker_orders'][order]['request']['base_amount'] 502 | rel_amount = my_current_orders['taker_orders'][order]['request']['rel_amount'] 503 | price = float(rel_amount)/float(base_amount) 504 | volume = base_amount 505 | try: 506 | market_rate = coins_data[base]['BTC_price']/coins_data[rel]['BTC_price'] 507 | except: 508 | market_rate = 0 509 | pair = rel+"/"+base 510 | btc_price = coins_data[rel]['BTC_price'] 511 | aud_price = coins_data[rel]['AUD_price'] 512 | usd_price = coins_data[rel]['USD_price'] 513 | if market_rate != 0: 514 | differential = float(market_rate)/float(price)-1 515 | else: 516 | differential = 0 517 | diff_pct = str(differential*100)[:5]+"%" 518 | if differential < 0: 519 | differential = colorize('{:^16}'.format(str(diff_pct)[:8]), 'green') 520 | elif differential > 0.07: 521 | differential = colorize('{:^16}'.format(str(diff_pct)[:8]), 'red') 522 | else: 523 | differential = colorize('{:^16}'.format(str(diff_pct)[:8]), 'default') 524 | price_source = coins_data[base]['price_source'] 525 | btc_price = str(btc_price) 526 | if price_source == 'binance': 527 | btc_price = colorize('{:^18}'.format(str(btc_price)), 'yellow') 528 | elif price_source == 'coingecko': 529 | btc_price = colorize('{:^18}'.format(str(btc_price)), 'pink') 530 | elif price_source == 'mm2_orderbook': 531 | btc_price = colorize('{:^18}'.format(str(btc_price)), 'cyan') 532 | rel_price = float(price) 533 | print(colorize(" "+hl+'{:^11}'.format("["+str(i)+"]")+hl+'{:^14}'.format(order_type)+hl \ 534 | +'{:^14}'.format(pair)+hl+'{:^18}'.format(volume[:14])+hl \ 535 | +'{:^18}'.format("$"+str(usd_price)[:14])+hl+'{:^18}'.format("$"+str(aud_price)[:14])+hl \ 536 | +'{:^18}'.format(str(btc_price))+hl+'{:^18}'.format(str(rel_price)[:14])+hl \ 537 | +'{:^18}'.format(str(market_rate)[:14])+hl \ 538 | +str(differential)+hl, 'blue') \ 539 | ) 540 | i += 1 541 | print(" "+row) 542 | if not bot: 543 | while True: 544 | q = input(colorize("Select an order number to cancel a trade, Cancel [A]ll trades, or [E]xit to menu: ", 'orange')) 545 | if q == 'e' or q == 'E': 546 | break 547 | elif q == 'a' or q == 'A': 548 | resp = rpclib.cancel_all(node_ip, user_pass).json() 549 | print(colorize("All orders cancelled!","orange")) 550 | break 551 | else: 552 | try: 553 | selected = my_order_list[int(q)-1] 554 | print(selected) 555 | order_uuid = selected['uuid'] 556 | resp = rpclib.cancel_uuid(node_ip, user_pass, order_uuid).json() 557 | print(colorize("Order #"+q+" cancelled!","orange")) 558 | break 559 | except: 560 | print(colorize("Invalid selection, must be [E/e] or a number between 1 and "+str(len(my_order_list)), 'red')) 561 | pass 562 | except Exception as e: 563 | print("Orders error: "+str(e)) 564 | pass 565 | if not bot: 566 | wait_continue() 567 | 568 | def show_balances_table(node_ip, user_pass, coins_data='', bot=False): 569 | coin_status = rpclib.check_coins_status(node_ip, user_pass) 570 | active_coins = coin_status[3] 571 | if coins_data == '': 572 | coins_data = rpclib.build_coins_data(node_ip, user_pass, list(coinslib.coins.keys())) 573 | if len(active_coins) == 0: 574 | msg = colorize("No coins activated!", 'red') 575 | if not bot: 576 | wait_continue() 577 | #TODO: add highlights for bot buy/sell 578 | btc_total = 0 579 | usd_total = 0 580 | aud_total = 0 581 | header = hl+'{:^10}'.format('COIN')+hl+'{:^50}'.format('ADDRESS (green = bot trading)')+hl \ 582 | +'{:^11}'.format('BALANCE')+hl+'{:^16}'.format('BTC PRICE')+hl \ 583 | +'{:^11}'.format('BTC VALUE')+hl+'{:^11}'.format('USD PRICE')+hl \ 584 | +'{:^11}'.format('USD VALUE')+hl+'{:^11}'.format('AUD PRICE')+hl \ 585 | +'{:^11}'.format('AUD VALUE')+hl 586 | table_dash = "-"*152 587 | print(colorize(" "+table_dash, 'lightblue')) 588 | print(colorize(" "+header, 'lightblue')) 589 | print(colorize(" "+table_dash, 'lightblue')) 590 | for coin in coins_data: 591 | if coin in active_coins: 592 | try: 593 | balance_data = rpclib.my_balance(node_ip, user_pass, coin).json() 594 | addr = balance_data['address'] 595 | bal = float(balance_data['balance']) 596 | except Exception as e: 597 | addr = "RPC timed out! "+str(e) 598 | bal = 0 599 | pass 600 | btc_price = 0 601 | btc_val = 0 602 | usd_price = 0 603 | usd_val = 0 604 | aud_price = 0 605 | aud_val = 0 606 | try: 607 | if coin in coins_data: 608 | btc_price = float(coins_data[coin]['BTC_price']) 609 | btc_val = btc_price*bal 610 | usd_price = coins_data[coin]['USD_price'] 611 | usd_val = usd_price*bal 612 | aud_price = coins_data[coin]['AUD_price'] 613 | aud_val = aud_price*bal 614 | except Exception as e: 615 | pass 616 | ''' 617 | # some coins only returning btc price 618 | print(coin) 619 | print(coins_data[coin]) 620 | print(e) 621 | ''' 622 | btc_total += btc_val 623 | usd_total += usd_val 624 | aud_total += aud_val 625 | price_source = coins_data[coin]['price_source'] 626 | if price_source == 'binance': 627 | btc_price = colorize('{:^16}'.format(str(btc_price)[:8]), 'yellow') 628 | elif price_source == 'coingecko': 629 | btc_price = colorize('{:^16}'.format(str(btc_price)[:8]), 'pink') 630 | elif price_source == 'mm2_orderbook': 631 | btc_price = colorize('{:^16}'.format(str(btc_price)[:8]), 'cyan') 632 | if coin not in coinslib.trading_list: 633 | row = hl+'{:^10}'.format(coin)+hl+'{:^50}'.format(addr)+hl \ 634 | +'{:^11}'.format(str(bal)[:9])+hl \ 635 | +'{:^16}'.format(str(btc_price))+hl+'{:^11}'.format(str(btc_val)[:9])+hl \ 636 | +'{:^11}'.format(str(usd_price)[:9])+hl+'{:^11}'.format(str(usd_val)[:9])+hl \ 637 | +'{:^11}'.format(str(aud_price)[:9])+hl+'{:^11}'.format(str(aud_val)[:9])+hl 638 | print(colorize(" "+row, 'lightblue')) 639 | else: 640 | row = hl+colorize('{:^10}'.format(coin),'green')+hl+colorize('{:^50}'.format(addr),'green')+hl \ 641 | +colorize('{:^11}'.format(str(bal)[:9]),'green')+hl \ 642 | +colorize('{:^16}'.format(str(btc_price)),'green')+hl+colorize('{:^11}'.format(str(btc_val)[:9]),'green')+hl\ 643 | +colorize('{:^11}'.format(str(usd_price)[:9]),'green')+hl+colorize('{:^11}'.format(str(usd_val)[:9]),'green')+hl\ 644 | +colorize('{:^11}'.format(str(aud_price)[:9]),'green')+hl+colorize('{:^11}'.format(str(aud_val)[:9]),'green')+hl 645 | print(colorize(" "+row, 'lightblue')) 646 | print(colorize(" "+table_dash, 'lightblue')) 647 | row = hl+'{:^78}'.format(' ')+hl \ 648 | +'{:^11}'.format('TOTAL BTC')+hl+'{:^11}'.format(str(btc_total)[:9])+hl \ 649 | +'{:^11}'.format('TOTAL USD')+hl+'{:^11}'.format(str(usd_total)[:9])+hl \ 650 | +'{:^11}'.format('TOTAL AUD')+hl+'{:^11}'.format(str(aud_total)[:9])+hl 651 | print(colorize(" "+row, 'lightblue')) 652 | print(colorize(" "+table_dash+"\n\n", 'lightblue')) 653 | if not bot: 654 | while True: 655 | outcome = withdraw_tui(node_ip, user_pass, active_coins) 656 | if outcome == 'back to menu': 657 | break 658 | 659 | def withdraw_tui(node_ip, user_pass, active_coins): 660 | q = input(colorize("[W]ithdraw funds, or [E]xit to menu: ", 'orange')) 661 | if q == 'e' or q == 'E': 662 | return 'back to menu' 663 | elif q == 'w' or q == 'W': 664 | while True: 665 | cointag = select_coin("Select coin to withdraw funds: ", active_coins) 666 | address = input(colorize("Enter destination "+cointag+" address: ",'orange')) 667 | amount = input(colorize("Enter amount to send, or [A] for all: ",'orange')) 668 | if amount == 'A' or amount == 'a': 669 | resp = rpclib.withdraw_all(node_ip, user_pass, cointag, address).json() 670 | else: 671 | resp = rpclib.withdraw(node_ip, user_pass, cointag, address, amount).json() 672 | if 'error' in resp: 673 | if resp['error'].find("Invalid Address") > 0: 674 | print(colorize("Invalid address! Try again...", 'red')) 675 | elif resp['error'].find("Not sufficient balance") > 0: 676 | print(colorize("Insufficient balance! Try again...", 'red')) 677 | else: 678 | print(colorize("Error: "+str(resp['error']), 'red')) 679 | else: 680 | txid = rpclib.send_raw_transaction(node_ip, user_pass, cointag, resp['tx_hex']).json() 681 | if 'tx_hash' in txid: 682 | if address.startswith('0x'): 683 | txid_str = '0x'+txid['tx_hash'] 684 | else: 685 | txid_str = txid['tx_hash'] 686 | print(colorize("Withdrawl successful! TXID: ["+txid_str+"]", 'green')) 687 | try: 688 | print(colorize("Track transaction status at ["+coinslib.coins[cointag]['tx_explorer']+"/"+txid_str+"]", 'cyan')) 689 | except: 690 | pass 691 | break 692 | else: 693 | print(colorize("Error: "+str(txid), 'red')) 694 | break 695 | else: 696 | print(colorize("Invalid selection, must be [E/e] or [W/w]! Try again...", 'red')) 697 | return 'try again' 698 | 699 | 700 | def show_pending_swaps(node_ip, user_pass, swapcount=50): 701 | header_list = [] 702 | swap_json = [] 703 | pending_swaps = [] 704 | error_events = ['StartFailed', 'NegotiateFailed', 'TakerFeeValidateFailed', 705 | 'MakerPaymentTransactionFailed', 'MakerPaymentDataSendFailed', 706 | 'TakerPaymentValidateFailed', 'TakerPaymentSpendFailed', 707 | 'MakerPaymentRefundFailed'] 708 | recent_swaps = rpclib.my_recent_swaps(node_ip, user_pass, swapcount).json() 709 | swap_list = recent_swaps['result']['swaps'] 710 | for swap in swap_list: 711 | ignore = False 712 | for event in swap['events']: 713 | if event['event']['type'] in error_events or event['event']['type'] == 'Finished': 714 | ignore = True 715 | if not ignore: 716 | pending_swaps.append(swap) 717 | if len(pending_swaps) > 0: 718 | pending_swaps_summary = {} 719 | for swap in pending_swaps: 720 | try: 721 | pending_swap_json = {} 722 | swap_type = swap['type'] 723 | swap_info = swap['my_info'] 724 | uuid = swap['uuid'] 725 | pending_swap_json.update({'swap_type':swap_type}) 726 | pending_swap_json.update({'uuid':uuid}) 727 | if swap_type == 'Taker': 728 | pending_swap_json.update({'taker_amount':swap_info['my_amount'][:8]}) 729 | pending_swap_json.update({'taker_coin':swap_info['my_coin']}) 730 | pending_swap_json.update({'maker_amount':swap_info['other_amount'][:8]}) 731 | pending_swap_json.update({"maker_coin":swap_info['other_coin']}) 732 | else: 733 | pending_swap_json.update({'maker_amount':swap_info['my_amount'][:8]}) 734 | pending_swap_json.update({'maker_coin':swap_info['my_coin']}) 735 | pending_swap_json.update({'taker_amount':swap_info['other_amount'][:8]}) 736 | pending_swap_json.update({"taker_coin":swap_info['other_coin']}) 737 | for event in swap['events']: 738 | pending_swap_json.update({"last_event":event['event']['type']}) 739 | pending_swaps_summary[uuid] = pending_swap_json 740 | except: 741 | pass 742 | 743 | header = hl+'{:^7}'.format('NUM')+hl+'{:^40}'.format('UUID')+hl+'{:^12}'.format('TYPE')+hl \ 744 | +'{:^34}'.format('CURRENT EVENT')+hl \ 745 | +'{:^12}'.format('TAKER COIN')+hl+'{:^12}'.format('TAKER VAL')+hl \ 746 | +'{:^12}'.format('MAKER COIN')+hl+'{:^12}'.format('MAKER VAL')+hl 747 | table_dash = "-"*150 748 | print(colorize(" "+table_dash, 'lightblue')) 749 | print(colorize(" "+header, 'lightblue')) 750 | print(colorize(" "+table_dash, 'lightblue')) 751 | i = 1 752 | for uuid in pending_swaps_summary: 753 | swap_summary = pending_swaps_summary[uuid] 754 | row = hl+'{:^7}'.format("["+str(i)+"]")+hl+'{:^40}'.format(uuid)+hl+'{:^12}'.format(swap_summary['swap_type'])+hl \ 755 | +'{:^34}'.format(swap_summary['last_event'])+hl \ 756 | +'{:^12}'.format(swap_summary['taker_coin'])+hl+'{:^12}'.format(swap_summary['taker_amount'])+hl \ 757 | +'{:^12}'.format(swap_summary['maker_coin'])+hl+'{:^12}'.format(swap_summary['maker_amount'])+hl 758 | print(colorize(" "+row, 'lightblue')) 759 | print(colorize(" "+table_dash, 'lightblue')) 760 | #print(error) 761 | i += 1 762 | while True: 763 | q = input(colorize("Enter a swap number to view events log, or [E]xit to menu: ", 'orange')) 764 | if q == 'e' or q == 'E': 765 | return 'back to menu' 766 | else: 767 | try: 768 | swap = pending_swaps[int(q)-1] 769 | for event in swap['events']: 770 | print(colorize("["+event['event']['type']+"]", 'green')) 771 | print(colorize(str(event), 'blue')) 772 | print() 773 | except Exception as e: 774 | print(colorize("Invalid selection, must be [E/e] or a number between 1 and "+str(len(pending_swaps)), 'red')) 775 | pass 776 | else: 777 | print(colorize("You have no pending swaps in your history!", 'orange')) 778 | wait_continue() 779 | 780 | def swaps_info(node_ip, user_pass, swapcount=250): 781 | recent_swaps = rpclib.my_recent_swaps(node_ip, user_pass, swapcount).json() 782 | swap_list = recent_swaps['result']['swaps'] 783 | swap_json = [] 784 | num_finished = 0 785 | num_in_progress = 0 786 | num_failed = 0 787 | num_swaps = 0 788 | header_list = [] 789 | if len(swap_list) > 0: 790 | error_events = ['StartFailed', 'NegotiateFailed', 'TakerFeeValidateFailed', 791 | 'MakerPaymentTransactionFailed', 'MakerPaymentDataSendFailed', 792 | 'TakerPaymentValidateFailed', 'TakerPaymentSpendFailed', 793 | 'MakerPaymentRefunded', 'MakerPaymentRefundFailed'] 794 | for swap in swap_list: 795 | try: 796 | taker_addr = '' 797 | maker_coin = '' 798 | maker_amount = 0 799 | taker_coin = '' 800 | taker_amount = 0 801 | role = swap['type'] 802 | swap_data = swap['events'][0] 803 | if 'maker_coin' in swap_data['event']['data']: 804 | maker_coin = swap_data['event']['data']['maker_coin'] 805 | if maker_coin not in header_list: 806 | header_list.append(maker_coin) 807 | if 'maker_amount' in swap_data['event']['data']: 808 | maker_amount = swap_data['event']['data']['maker_amount'] 809 | if 'taker_coin' in swap_data['event']['data']: 810 | taker_coin = swap_data['event']['data']['taker_coin'] 811 | if taker_coin not in header_list: 812 | header_list.append(taker_coin) 813 | if 'taker_amount' in swap_data['event']['data']: 814 | taker_amount = swap_data['event']['data']['taker_amount'] 815 | timestamp = int(int(swap_data['timestamp'])/1000) 816 | human_time = time.ctime(timestamp) 817 | for event in swap['events']: 818 | if event['event']['type'] == 'Started': 819 | if 'taker' in swap_data['event']['data']: 820 | taker_pub = swap_data['event']['data']['taker'] 821 | taker_addr = get_radd_from_pub(taker_pub) 822 | else: 823 | taker_pub = swap_data['event']['data']['maker'] 824 | taker_addr = get_radd_from_pub(taker_pub) 825 | if event['event']['type'] in error_events: 826 | swap_status = event['event']['type'] 827 | break 828 | else: 829 | swap_status = event['event']['type'] 830 | if taker_addr not in ignored_addresses: 831 | swap_json.append({"result":swap_status, 832 | "time":human_time, 833 | "role":role, 834 | "maker_coin":maker_coin, 835 | "maker_amount":maker_amount, 836 | "taker_coin":taker_coin, 837 | "taker_amount":taker_amount 838 | }) 839 | except Exception as e: 840 | print(e) 841 | pass 842 | num_swaps = len(swap_json) 843 | for swap in swap_json: 844 | if swap['result'] == 'Finished': 845 | num_finished += 1 846 | elif swap['result'].find('Failed') != -1: 847 | num_failed += 1 848 | else: 849 | num_in_progress += 1 850 | return swap_json, num_swaps, num_finished, num_failed, num_in_progress, header_list 851 | 852 | def show_recent_swaps(node_ip, user_pass, swapcount=50, coins_data='', bot=False): 853 | print(colorize("Getting swaps info...", 'cyan')) 854 | swap_info = swaps_info(node_ip, user_pass) 855 | swap_json = swap_info[0] 856 | header_list = swap_info[5] 857 | if len(swap_json) > 0: 858 | if coins_data == '': 859 | coins_data = rpclib.build_coins_data(node_ip, user_pass) 860 | delta = {} 861 | header = "|"+'{:^14}'.format("TIME")+"|"+'{:^8}'.format("RESULT")+"|"+'{:^7}'.format("ROLE")+"|" 862 | for coin in header_list: 863 | header += '{:^8}'.format(coin)+"|" 864 | delta[coin] = 0 865 | table_dash = "-"*len(header) 866 | print(" "+table_dash) 867 | print(" "+header) 868 | print(" "+table_dash) 869 | for swap in swap_json: 870 | role = swap['role'] 871 | time_str = swap['time'][:-8] 872 | time_str = time_str[4:] 873 | row_str = "|"+'{:^14}'.format(time_str)+"|" 874 | if swap['result'].find('Failed') > 0: 875 | highlight = 'red' 876 | result = 'FAILED' 877 | elif swap['result'].find('Finished') > -1: 878 | highlight = 'green' 879 | result = 'FINISHED' 880 | else: 881 | highlight = 'orange' 882 | result = 'PENDING' 883 | result = colorize('{:^8}'.format(result), highlight)+"|" 884 | row_str += result 885 | row_str += '{:^7}'.format(role)+"|" 886 | for coin in header_list: 887 | if role == 'Maker': 888 | if coin == swap['maker_coin']: 889 | swap_amount = float(swap['maker_amount'])*-1 890 | elif coin == swap['taker_coin']: 891 | swap_amount = float(swap['taker_amount']) 892 | else: 893 | swap_amount = 0 894 | elif role == 'Taker': 895 | if coin == swap['taker_coin']: 896 | swap_amount = float(swap['taker_amount'])*-1 897 | elif coin == swap['maker_coin']: 898 | swap_amount = float(swap['maker_amount']) 899 | else: 900 | swap_amount = 0 901 | if result.find('Failed') == -1: 902 | delta[coin] += swap_amount 903 | if swap_amount < 0: 904 | row_str += colorize('{:^8}'.format(str(swap_amount)[:6]), 'red')+"|" 905 | elif swap_amount > 0: 906 | row_str += colorize('{:^8}'.format(str(swap_amount)[:6]), 'green')+"|" 907 | else: 908 | row_str += colorize('{:^8}'.format('-'), 'darkgrey')+"|" 909 | print(" "+row_str) 910 | delta_row = "|"+'{:^31}'.format("TOTAL")+"|" 911 | btc_row = "|"+'{:^31}'.format("BTC")+"|" 912 | usd_row = "|"+'{:^31}'.format("USD")+"|" 913 | aud_row = "|"+'{:^31}'.format("AUD")+"|" 914 | table_dash = "-"*(len(delta_row)+(len(header_list)+1)*9) 915 | btc_sum = 0 916 | usd_sum = 0 917 | aud_sum = 0 918 | for delta_coin in delta: 919 | try: 920 | for header_coin in header_list: 921 | if delta_coin == header_coin: 922 | btc_price = coins_data[header_coin]['BTC_price']*delta[header_coin] 923 | usd_price = coins_data[header_coin]['USD_price']*delta[header_coin] 924 | aud_price = coins_data[header_coin]['AUD_price']*delta[header_coin] 925 | btc_sum += btc_price 926 | usd_sum += usd_price 927 | aud_sum += aud_price 928 | if float(delta[header_coin]) > 0: 929 | highlight = 'green' 930 | else: 931 | highlight = 'red' 932 | delta_row += colorize('{:^8}'.format(str(delta[header_coin])[:6]),highlight)+"|" 933 | btc_row += colorize('{:^8}'.format(str(btc_price)[:6]),highlight)+"|" 934 | usd_row += colorize('{:^8}'.format("$"+str(usd_price)[:5]),highlight)+"|" 935 | aud_row += colorize('{:^8}'.format("$"+str(aud_price)[:5]),highlight)+"|" 936 | except: 937 | pass 938 | delta_row += '{:^8}'.format("TOTAL")+"|" 939 | btc_row += '{:^8}'.format(str(btc_sum)[:6])+"|" 940 | usd_row += '{:^8}'.format("$"+str(usd_sum)[:5])+"|" 941 | aud_row += '{:^8}'.format("$"+str(aud_sum)[:5])+"|" 942 | 943 | print(" "+table_dash) 944 | print(" "+delta_row) 945 | print(" "+table_dash) 946 | print(" "+btc_row) 947 | print(" "+table_dash) 948 | print(" "+usd_row) 949 | print(" "+table_dash) 950 | print(" "+aud_row) 951 | print(" "+table_dash) 952 | #calculate in / out value 953 | else: 954 | print(colorize("You have no swaps in your history!", 'orange')) 955 | if not bot: 956 | wait_continue() 957 | 958 | def recover_swap(node_ip, user_pass): 959 | uuid = input(colorize("Enter stuck swap UUID: ", 'orange')) 960 | print(uuid) 961 | resp = rpclib.recover_stuck_swap(node_ip, user_pass, uuid).json() 962 | print(resp) 963 | wait_continue() 964 | 965 | def show_failed_swaps(node_ip, user_pass, swapcount=50): 966 | header_list = [] 967 | swap_json = [] 968 | failed_swaps = [] 969 | error_events = ['StartFailed', 'NegotiateFailed', 'TakerFeeValidateFailed', 970 | 'MakerPaymentTransactionFailed', 'MakerPaymentDataSendFailed', 971 | 'TakerPaymentValidateFailed', 'TakerPaymentSpendFailed', 972 | 'MakerPaymentRefundFailed'] 973 | recent_swaps = rpclib.my_recent_swaps(node_ip, user_pass, swapcount).json() 974 | swap_list = recent_swaps['result']['swaps'] 975 | for swap in swap_list: 976 | for event in swap['events']: 977 | if event['event']['type'] in error_events: 978 | failed_swaps.append(swap) 979 | break 980 | if len(failed_swaps) > 0: 981 | failed_swaps_summary = {} 982 | for swap in failed_swaps: 983 | try: 984 | timestamps_list = [] 985 | errors_list = [] 986 | failed_swap_json = {} 987 | swap_type = swap['type'] 988 | uuid = swap['uuid'] 989 | failed_swap_json.update({'swap_type':swap_type}) 990 | failed_swap_json.update({'uuid':uuid}) 991 | for event in swap['events']: 992 | event_type = event['event']['type'] 993 | event_timestamp = event['timestamp'] 994 | timestamps_list.append({event_type:event_timestamp}) 995 | if event['event']['type'] in error_events: 996 | error = str(event['event']['data']) 997 | errors_list.append({event_type:error}) 998 | if event['event']['type'] == 'Started': 999 | if 'taker' in event['event']['data']: 1000 | taker_pub = event['event']['data']['taker'] 1001 | taker_addr = get_radd_from_pub(taker_pub) 1002 | else: 1003 | taker_pub = event['event']['data']['maker'] 1004 | taker_addr = get_radd_from_pub(taker_pub) 1005 | failed_swap_json.update({'lock_duration':event['event']['data']['lock_duration']}) 1006 | failed_swap_json.update({'taker_coin':event['event']['data']['taker_coin']}) 1007 | failed_swap_json.update({'taker_addr':taker_addr}) 1008 | failed_swap_json.update({'maker_coin':event['event']['data']['maker_coin']}) 1009 | failed_swap_json.update({'maker_pub':event['event']['data']['my_persistent_pub']}) 1010 | if 'data' in event['event']: 1011 | if 'maker_payment_locktime' in event['event']['data']: 1012 | failed_swap_json.update({'maker_locktime':event['event']['data']['maker_payment_locktime']}) 1013 | if 'taker_payment_locktime' in event['event']['data']: 1014 | failed_swap_json.update({'taker_locktime':event['event']['data']['taker_payment_locktime']}) 1015 | if event['event']['type'] == 'Finished': 1016 | failed_swap_json.update({'timestamps_list':timestamps_list}) 1017 | failed_swap_json.update({'errors':errors_list}) 1018 | if taker_addr not in ignored_addresses: 1019 | failed_swaps_summary[uuid] = failed_swap_json 1020 | except Exception as e: 1021 | print("swap summary error: "+str(e)) 1022 | pass 1023 | 1024 | header = hl+'{:^7}'.format('NUM')+hl+'{:^40}'.format('UUID')+hl+'{:^7}'.format('TYPE')+hl \ 1025 | +'{:^28}'.format('FAIL EVENT')+hl+'{:^23}'.format('ERROR')+hl \ 1026 | +'{:^7}'.format('TAKER')+hl+'{:^7}'.format('MAKER')+hl \ 1027 | +'{:^66}'.format('TAKER RADD')+hl 1028 | table_dash = "-"*194 1029 | print(colorize(" "+table_dash, 'lightblue')) 1030 | print(colorize(" "+header, 'lightblue')) 1031 | print(colorize(" "+table_dash, 'lightblue')) 1032 | i = 1 1033 | for uuid in failed_swaps_summary: 1034 | swap_summary = failed_swaps_summary[uuid] 1035 | for error in swap_summary['errors']: 1036 | taker_addr = '' 1037 | taker_coin = '' 1038 | maker_coin = '' 1039 | #swap_time = swap_summary['timestamps_list'][0][1]-swap_summary['timestamps_list'][0][0] 1040 | start_time = list(swap_summary['timestamps_list'][0].values())[0] 1041 | end_time = list(swap_summary['timestamps_list'][-1].values())[0] 1042 | swap_time = ((end_time - start_time)/1000)/60 1043 | if 'taker_addr' in swap_summary: 1044 | taker_addr = swap_summary['taker_addr'] 1045 | if 'taker_coin' in swap_summary: 1046 | taker_coin = swap_summary['taker_coin'] 1047 | if 'maker_coin' in swap_summary: 1048 | maker_coin = swap_summary['maker_coin'] 1049 | if str(error).find('overwinter') > 0: 1050 | error_type = "tx-overwinter-active" 1051 | elif str(error).find('timeout') > 0: 1052 | error_type = "timeout" 1053 | else: 1054 | error_type = "other" 1055 | row = hl+'{:^7}'.format("["+str(i)+"]")+hl+'{:^40}'.format(uuid)+hl+'{:^7}'.format(str(swap_type))+hl \ 1056 | +'{:^28}'.format(str(list(error.keys())[0]))+hl+'{:^23}'.format(error_type)+hl \ 1057 | +'{:^7}'.format(taker_coin)+hl+'{:^7}'.format(maker_coin)+hl \ 1058 | +'{:^66}'.format(taker_addr)+hl 1059 | print(colorize(" "+row, 'lightblue')) 1060 | print(colorize(" "+table_dash, 'lightblue')) 1061 | #print(error) 1062 | i += 1 1063 | while True: 1064 | q = input(colorize("Enter a swap number to view events log, or [E]xit to menu: ", 'orange')) 1065 | if q == 'e' or q == 'E': 1066 | return 'back to menu' 1067 | else: 1068 | try: 1069 | swap = failed_swaps[int(q)-1] 1070 | for event in swap['events']: 1071 | print(colorize("["+event['event']['type']+"]", 'green')) 1072 | if event['event']['type'] in error_events: 1073 | print(colorize(str(event), 'red')) 1074 | else: 1075 | print(colorize(str(event), 'blue')) 1076 | except Exception as e: 1077 | print(colorize("Invalid selection, must be [E/e] or a number between 1 and "+str(len(failed_swaps)), 'red')) 1078 | pass 1079 | else: 1080 | print(colorize("You have no failed swaps in your history!", 'orange')) 1081 | wait_continue() 1082 | 1083 | def run_tradebot(node_ip, user_pass, refresh_mins=20): 1084 | while True: 1085 | try: 1086 | coins_data = rpclib.build_coins_data(node_ip, user_pass, list(coinslib.coins.keys())) 1087 | submit_bot_trades(node_ip, user_pass, coins_data) 1088 | show_orders_table(node_ip, user_pass, coins_data, True) 1089 | show_balances_table(node_ip, user_pass, coins_data, True) 1090 | show_recent_swaps(node_ip, user_pass, 50, coins_data, True) 1091 | for i in range(refresh_mins): 1092 | time_left = refresh_mins-i 1093 | print(str(time_left)+"min until refresh..." ) 1094 | print("Exit bot with [CTRL-C]" ) 1095 | time.sleep(60) 1096 | except KeyboardInterrupt: 1097 | break 1098 | while True: 1099 | q = input(colorize("Cancel all orders (y/n)? ", 'orange')) 1100 | if q == 'n' or q == 'N': 1101 | break 1102 | elif q == 'y' or q == 'Y': 1103 | resp = rpclib.cancel_all(node_ip, user_pass).json() 1104 | print(colorize("All orders cancelled!","orange")) 1105 | break 1106 | else: 1107 | print(colorize("Invalid selection, must be [Y/y] or [N/n]", 'red')) 1108 | pass 1109 | 1110 | def get_btc_price(cointag): 1111 | if cointag == 'BTC': 1112 | return 1 1113 | if cointag == 'BCH': 1114 | btc_price = binance_api.get_price('BCHABCBTC') 1115 | else: 1116 | btc_price = binance_api.get_price(cointag+'BTC') 1117 | if 'price' in btc_price: 1118 | return btc_price['price'] 1119 | else: 1120 | return 0 1121 | 1122 | def get_binance_addr(cointag): 1123 | try: 1124 | if cointag == "BCH": 1125 | deposit_addr = binance_api.get_deposit_addr(cointag+"ABC") 1126 | else: 1127 | deposit_addr = binance_api.get_deposit_addr(cointag) 1128 | except: 1129 | deposit_addr == '' 1130 | pass 1131 | return deposit_addr 1132 | 1133 | def binance_account_info(node_ip='', user_pass='', base='', bal=0, base_addr='', coins_data='', bot=False): 1134 | if base == '': 1135 | coinslist = coinslib.cointags 1136 | coins_data = rpclib.build_coins_data(node_ip, user_pass) 1137 | else: 1138 | coinslist = [base] 1139 | for base in coinslist: 1140 | try: 1141 | account_info = binance_api.get_account_info() 1142 | usd_price = coins_data[base]['USD_price'] 1143 | binance_balances = account_info['balances'] 1144 | atomicDEX_reserve = coinslib.coins[base]['reserve_balance'] 1145 | if bot: 1146 | for item in binance_balances: 1147 | if item['asset'] == base: 1148 | binance_balance = float(item['free']) 1149 | print(base+" balance on Binance is: "+str(binance_balance)) 1150 | print(base+" balance on AtomicDEX is: "+str(bal)) 1151 | print(base+" reserve for AtomicDEX is: "+str(atomicDEX_reserve)) 1152 | break 1153 | else: 1154 | binance_balance = 0 1155 | try: 1156 | if bal > atomicDEX_reserve*1.2 and base not in ['RICK','MORTY']: 1157 | qty = bal - atomicDEX_reserve 1158 | bal = bal - qty 1159 | deposit_addr = get_binance_addr(base) 1160 | withdraw_tx = rpclib.withdraw(node_ip, user_pass, base, deposit_addr['address'], qty).json() 1161 | print("Sending "+str(qty)+" "+base+" to Binance address "+deposit_addr['address']) 1162 | send_resp = rpclib.send_raw_transaction(node_ip, user_pass, base, withdraw_tx['tx_hex']).json() 1163 | if 'tx_hash' in send_resp: 1164 | txid_str = send_resp['tx_hash'] 1165 | print(colorize("Track transaction status at ["+coinslib.coins[base]['tx_explorer']+"/"+txid_str+"]", 'cyan')) 1166 | else: 1167 | print("Response: "+str(send_resp)) 1168 | elif bal < atomicDEX_reserve*0.9 and base not in ['RICK','MORTY']: 1169 | qty = atomicDEX_reserve - bal 1170 | if base_addr != '': 1171 | if qty > binance_balance: 1172 | qty = binance_balance 1173 | qty = round(float(qty), 8) 1174 | if binance_balance >= qty and qty > 0: 1175 | print("Withdrawing "+str(qty)+" of "+str(binance_balance)+" "+base+" from Binance to address "+base_addr) 1176 | if base == "BCH": 1177 | withdraw_tx = binance_api.withdraw(base+"ABC", base_addr, qty) 1178 | else: 1179 | withdraw_tx = binance_api.withdraw(base, base_addr, qty) 1180 | print(withdraw_tx) 1181 | else: 1182 | print("Binance balance "+str(binance_balance)+" too low to reach AtomicDEX reserve "+str(atomicDEX_reserve)+" ...") 1183 | pass 1184 | except Exception as e: 1185 | print(deposit_addr) 1186 | print(e) 1187 | print('Binance deposit/withdraw failed') 1188 | pass 1189 | else: 1190 | for item in binance_balances: 1191 | if item['asset'] == base: 1192 | binance_balance = float(item['free']) 1193 | print(base+" balance on Binance is: "+str(binance_balance)) 1194 | break 1195 | else: 1196 | binance_balance = 0 1197 | except Exception as e: 1198 | print(e) 1199 | input(colorize("Press [Enter] to continue...", 'orange')) 1200 | pass 1201 | if not bot: 1202 | input(colorize("Press [Enter] to continue...", 'orange')) 1203 | return bal 1204 | 1205 | def submit_bot_trades(node_ip, user_pass, coins_data): 1206 | swaps_in_progress = 0 1207 | for base in coinslib.sell_list: 1208 | try: 1209 | balance_data = rpclib.my_balance(node_ip, user_pass, base).json() 1210 | base_addr = balance_data['address'] 1211 | total_bal = float(balance_data['balance']) 1212 | locked_bal = float(balance_data['locked_by_swaps']) 1213 | bal = total_bal - locked_bal 1214 | except: 1215 | print("my_balance failed!") 1216 | print(balance_data) 1217 | base_addr = '' 1218 | bal = 0 1219 | pass 1220 | bal = binance_account_info(node_ip, user_pass, base, bal, base_addr, coins_data, True) 1221 | my_current_orders = rpclib.my_orders(node_ip, user_pass).json()['result'] 1222 | for rel in coinslib.buy_list: 1223 | if rel != base: 1224 | if 'override_KMD_buy_price' in coinslib.coins[rel]: 1225 | rel_btc_price = float(get_btc_price('KMD'))*float(coinslib.coins[rel]['override_KMD_buy_price']) 1226 | else: 1227 | rel_btc_price = get_btc_price(rel) 1228 | if rel_btc_price != 0 or rel in ['RICK', 'MORTY']: 1229 | trade_vol=bal*0.97 1230 | for order in my_current_orders['maker_orders']: 1231 | if base == my_current_orders['maker_orders'][order]['base'] and rel == my_current_orders['maker_orders'][order]['rel']: 1232 | started_swaps = my_current_orders['maker_orders'][order]['started_swaps'] 1233 | swaps_in_progress = len(started_swaps) 1234 | if swaps_in_progress > 0: 1235 | print(str(swaps_in_progress)+" x "+base+" to "+rel+" swaps in order!") 1236 | for swap_uuid in started_swaps: 1237 | swap_data = rpclib.my_swap_status(node_ip, user_pass, swap_uuid).json() 1238 | for event in swap_data['result']['events']: 1239 | if event['event']['type'] == 'Finished': 1240 | print(swap_uuid+" finished") 1241 | swaps_in_progress -= 1; 1242 | if swaps_in_progress == 0: 1243 | if 'override_KMD_sell_price' in coinslib.coins[base]: 1244 | base_btc_price = float(get_btc_price('KMD'))*float(coinslib.coins[base]['override_KMD_sell_price']) 1245 | else: 1246 | base_btc_price = get_btc_price(base) 1247 | if base_btc_price != 0 or base in ['RICK', 'MORTY']: 1248 | if base == 'BTC': 1249 | rel_price = 1 1250 | else: 1251 | base_price = base_btc_price 1252 | if rel == 'BTC': 1253 | rel_price = 1 1254 | else: 1255 | rel_price = rel_btc_price 1256 | if base not in ['RICK', 'MORTY'] and rel not in ['RICK', 'MORTY']: 1257 | pair_price = float(base_price)/float(rel_price) 1258 | else: 1259 | pair_price = 1 1260 | try: 1261 | min_swap = coinslib.coins[base]['min_swap'] 1262 | except: 1263 | min_swap = 0 1264 | if trade_vol > min_swap: 1265 | if base in ['RICK', 'MORTY'] or rel in ['RICK', 'MORTY']: 1266 | if base in ['RICK', 'MORTY'] and rel in ['RICK', 'MORTY']: 1267 | trade_price = 1.762 1268 | resp = rpclib.setprice(node_ip, user_pass, base, rel, trade_vol, trade_price).json() 1269 | print(colorize("Setprice order: "+str(trade_vol)[:8]+" "+base+" for "+str(trade_price*trade_vol)[:8]+" "+rel+" submitted...","red")) 1270 | time.sleep(0.1) 1271 | else: 1272 | if 'premium' in coinslib.coins[base]: 1273 | trade_price = pair_price*coinslib.coins[base]['premium'] 1274 | else: 1275 | trade_price = pair_price*1.05 1276 | resp = rpclib.setprice(node_ip, user_pass, base, rel, trade_vol, trade_price).json() 1277 | print(colorize("Setprice order: "+str(trade_vol)[:8]+" "+base+" for "+str(trade_price*trade_vol)[:8]+" "+rel+" submitted...","cyan")) 1278 | time.sleep(0.1) 1279 | else: 1280 | print(colorize("Unable to get price for "+base+", skipping...", 'cyan')) 1281 | else: 1282 | print(colorize("Unable to get price for "+rel+", skipping...", 'cyan')) 1283 | # TODO: Detect swaps in progress, and make sure to not cancel with new swap. --------------------------------------------------------------------------------