示例#1
0
def create_tor():
    from ansi_management import (warning, success, error, info, clear_screen,
                                 bold, muted, yellow, blue)
    from config import Config
    # ----------------------------------------------
    #                 Test Tor
    # ----------------------------------------------
    with yaspin(text="Testing Tor", color="cyan") as spinner:
        from connections import test_tor
        tor = test_tor()
        if tor['status']:
            logging.info(success("Tor Connected"))
            spinner.ok("✅ ")
            spinner.text = success("    Tor Connected [Success]")
            print("")
            return (tor)
        else:
            logging.error(error("Could not connect to Tor"))
            spinner.fail("💥 ")
            spinner.text = warning("    Tor NOT connected [ERROR]")
            print(error("    Could not connect to Tor."))

            print(
                info(
                    "    [i] If you have a Tor Browser installed try opening (leave it running) and try again."
                ))

            print(
                info("    [i] If you are running Linux try the command: ") +
                yellow('service tor start'))
            print(
                info(
                    "    or download Tor at: https://www.torproject.org/download/"
                ))
            print("")
示例#2
0
def create_tor():
    # ----------------------------------------------
    #                 Test Tor
    # ----------------------------------------------
    with yaspin(text="Testing Tor", color="cyan") as spinner:
        tor = test_tor()
        if tor['status']:
            logging.info(success("Tor Connected"))
            spinner.ok("✅ ")
            spinner.write(success("    Tor Connected [Success]"))
            print("")
            return (tor)
        else:
            logging.error(error("Could not connect to Tor"))
            spinner.fail("💥 ")
            spinner.write(warning("    Tor NOT connected [ERROR]"))
            print(
                error(
                    "    Could not connect to Tor. WARden requires Tor to run. Quitting..."
                ))
            print(
                info(
                    "    Download Tor at: https://www.torproject.org/download/"
                ))
            print("")
            exit()
示例#3
0
def data_large_price():
    from node_warden import load_config
    config = load_config(quiet=True)
    ft_config = config['MAIN']
    font = ft_config.get('large_text_font')
    btc = btc_price_data()
    try:
        btc_price = cleanfloat(btc['DISPLAY']['BTC']['USD']['PRICE'])
    except Exception:
        return (error(' >> Error getting price data. Retrying...'))
    custom_fig = pyfiglet.Figlet(font=font)
    return_fig = custom_fig.renderText('$  ' + jformat(btc_price, 0))
    return_fig = yellow(return_fig)

    chg_str = btc['DISPLAY']['BTC']['USD']['CHANGEPCTDAY']
    chg = cleanfloat(chg_str)
    msg = '\n'

    if chg >= 0:
        msg += success(f'24hr Change: +{chg_str}%\n')
    if chg > 5:
        msg += (info("[NgU] ") + muted(f"Looks like Bitcoin is pumping ") +
                emoji.emojize(":rocket:"))

    if chg < 0:
        msg += error(f'24hr Change: {chg_str}%\n')
    if chg < -5:
        msg += muted(
            f"Bitcoin dropping? Buy the dip!\nTime to stack some sats. ")

    return_fig = muted(return_fig)
    return_fig += msg

    return (return_fig)
示例#4
0
def data_random_satoshi():
    from node_warden import load_config
    config = load_config(quiet=True)
    url = config['QUOTES'].get('url')
    try:
        quotes = tor_request(url).json()
    except Exception:
        return (error(' >> Error contacting server. Retrying... '))
    quote = quotes[randrange(len(quotes))]
    return_str = info(f"Satoshi Quotes | Subject: {quote['category']}\n")
    return_str += muted(f"{quote['date']} on {quote['medium']}\n")
    return_str += yellow(f"{quote['text']} \n\n")
    return_str += muted("Source: Nakamoto Institute")
    return (return_str)
示例#5
0
def data_logger():
    from node_warden import debug_file
    lines = 8
    log_txt = tail(debug_file, lines)
    return_str = []
    for element in log_txt:
        if 'INFO' in element:
            return_str.append(info(element))
        elif 'ERROR' in log_txt:
            return_str.append(error(element))
        elif 'WARN' in log_txt:
            return_str.append(warning(element))
        else:
            return_str.append((element))
    return_str = ''.join(return_str)
    return return_str
示例#6
0
def data_logger():
    from node_warden import debug_file
    try:
        lines = 8
        log_txt = tail(debug_file, lines)
        return_str = []
        for element in log_txt:
            if 'INFO' in element:
                return_str.append(info(element))
            elif 'ERROR' in log_txt:
                return_str.append(error(element))
            elif 'WARN' in log_txt:
                return_str.append(warning(element))
            else:
                return_str.append((element))
        return_str = ''.join(return_str)
    except Exception:
        return yellow('>> Error Loading Log Messages. Retying...')
    return return_str
示例#7
0
    def check_for_pump(_loop, _data):
        try:
            btc = btc_price_data()
            btc_price = btc['DISPLAY']['BTC']['USD']['PRICE']
            chg_str = btc['DISPLAY']['BTC']['USD']['CHANGEPCTDAY']
            chg = cleanfloat(chg_str)
            if chg > 5:
                logging.info(
                    info("[NgU] ") + muted(f"Looks like Bitcoin is pumping ") +
                    emoji.emojize(":rocket:") + yellow(f' {btc_price}') +
                    success(f' +{chg_str}%'))
            if chg < -5:
                logging.info(
                    info("[NgU] ") + muted(
                        f"Looks like Bitcoin is dropping. Time to stack some sats. "
                    ) + yellow(f' {btc_price}') + error(f' {chg_str}%'))
        except Exception:
            pass

        main_loop.set_alarm_in(300, check_for_pump)
示例#8
0
def load_config(quiet=False):
    # Load Config
    config_file = os.path.join(basedir, 'config.ini')
    CONFIG = configparser.ConfigParser()
    if quiet:
        CONFIG.read(config_file)
        return (CONFIG)

    with yaspin(text="Loading config.ini", color="cyan") as spinner:

        # Check that config file exists
        if os.path.isfile(config_file):
            CONFIG.read(config_file)
            spinner.ok("✅ ")
            spinner.write(success("    Config Loaded [Success]"))
            print("")
            return (CONFIG)
        else:
            spinner.fail("💥 ")
            spinner.write(
                warning("    Config file could not be loaded [ERROR]"))
            print(error("    WARden requires config.ini to run. Quitting..."))
            exit()
示例#9
0
def init_app(app):
    from ansi_management import (warning, success, error)
    from utils import (create_config, runningInDocker)
    from config import Config
    from connections import tor_request
    warnings.filterwarnings('ignore')

    # Load config.ini into app
    # --------------------------------------------
    # Read Global Variables from warden.config(s)
    # Can be accessed like a dictionary like:
    # app.settings['PORTFOLIO']['RENEW_NAV']
    # --------------------------------------------
    config_file = Config.config_file
    app.warden_status = {}

    # Check for internet connection
    internet_ok = internet_connected()
    if internet_ok is True:
        print(success("✅ Internet Connection"))
    else:
        print(
            error(
                "[!] WARden needs internet connection. Check your connection.")
        )
        print(warning("[!] Exiting"))
        exit()

    # Config
    config_settings = configparser.ConfigParser()
    if os.path.isfile(config_file):
        config_settings.read(config_file)
        app.warden_status['initial_setup'] = False
        print(
            success(
                "✅ Config Loaded from config.ini - edit it for customization"
            ))
    else:
        print(
            error(
                "  Config File could not be loaded, created a new one with default values..."
            ))
        create_config(config_file)
        config_settings.read(config_file)
        app.warden_status['initial_setup'] = True

    table_error = False
    try:
        # create empty instance of LoginManager
        app.login_manager = LoginManager()
    except sqlite3.OperationalError:
        table_error = True

    # Create empty instance of SQLAlchemy
    app.db = SQLAlchemy()
    app.db.init_app(app)
    # Import models so tables are created
    from models import Trades, User, AccountInfo, TickerInfo, SpecterInfo
    app.db.create_all()

    #  There was an initial error on getting users
    #  probably because tables were not created yet.
    # The above create_all should have solved it so try again.
    if table_error:
        # create empty instance of LoginManager
        app.login_manager = LoginManager()

    # If login required - go to login:
    app.login_manager.login_view = "warden.login"
    # To display messages - info class (Bootstrap)
    app.login_manager.login_message_category = "secondary"
    app.login_manager.init_app(app)

    # Create empty instance of messagehandler
    from message_handler import MessageHandler
    app.message_handler = MessageHandler()
    app.message_handler.clean_all()

    # Get Version
    print("")
    try:
        version_file = Config.version_file
        with open(version_file, 'r') as file:
            current_version = file.read().replace('\n', '')
    except Exception:
        current_version = 'unknown'
    with app.app_context():
        app.version = current_version

    # Check if there are any users on database, if not, needs initial setup
    users = User.query.all()
    if users == []:
        app.warden_status['initial_setup'] = True

    # Check for Cryptocompare API Keys
    print("")
    check_cryptocompare()
    print("")

    print(f"[i] Running WARden version: {current_version}")
    app.warden_status['running_version'] = current_version

    # CHECK FOR UPGRADE
    repo_url = 'https://api.github.com/repos/pxsocs/warden/releases'
    try:
        github_version = tor_request(repo_url).json()[0]['tag_name']
    except Exception:
        github_version = None

    app.warden_status['github_version'] = github_version

    if github_version:
        print(f"[i] Newest WARden version available: {github_version}")
        parsed_github = version.parse(github_version)
        parsed_version = version.parse(current_version)

        app.warden_status['needs_upgrade'] = False
        if parsed_github > parsed_version:
            print(warning("  [i] Upgrade Available"))
            app.warden_status['needs_upgrade'] = True
        if parsed_github == parsed_version:
            print(success("✅ You are running the latest version"))
    else:
        print(warning("[!] Could not check GitHub for updates"))

    print("")
    # Check if config.ini exists
    with app.app_context():
        app.settings = config_settings
    with app.app_context():
        try:
            from utils import fxsymbol
            app.fx = fxsymbol(config_settings['PORTFOLIO']['base_fx'], 'all')
        except KeyError:  # Problem with this config, reset
            print(error("  [!] Config File needs to be rebuilt"))
            print("")
            create_config(config_file)

    # TOR Server through Onion Address --
    # USE WITH CAUTION - ONION ADDRESSES CAN BE EXPOSED!
    # WARden needs to implement authentication (coming soon)
    if app.settings['SERVER'].getboolean('onion_server'):
        from stem.control import Controller
        from urllib.parse import urlparse
        app.tor_port = app.settings['SERVER'].getint('onion_port')
        app.port = app.settings['SERVER'].getint('port')
        from warden_modules import home_path
        toraddr_file = os.path.join(home_path(), "onion.txt")
        app.save_tor_address_to = toraddr_file
        proxy_url = "socks5h://localhost:9050"
        tor_control_port = ""
        try:
            tor_control_address = urlparse(proxy_url).netloc.split(":")[0]
            if tor_control_address == "localhost":
                tor_control_address = "127.0.0.1"
            app.controller = Controller.from_port(
                address=tor_control_address,
                port=int(tor_control_port) if tor_control_port else "default",
            )
        except Exception:
            app.controller = None
        from tor import start_hidden_service
        start_hidden_service(app)

    from routes import warden
    from errors.handlers import errors
    from api.routes import api
    from csv_routes.routes import csv_routes
    from user_routes.routes import user_routes
    from simulator.routes import simulator
    app.register_blueprint(warden)
    app.register_blueprint(errors)
    app.register_blueprint(api)
    app.register_blueprint(csv_routes)
    app.register_blueprint(user_routes)
    app.register_blueprint(simulator)

    # Prepare app to receive Specter Server info
    # For the first load, just get a saved file if available
    # The background jobs will update later
    with app.app_context():
        from specter_importer import Specter
        app.specter = Specter()
        app.specter.refresh_txs(load=True)
        app.downloading = False

    with app.app_context():
        app.runningInDocker = runningInDocker()

    with app.app_context():
        app.tor = create_tor()

    # Check if home folder exists, if not create
    home = str(Path.home())
    home_path = os.path.join(home, 'warden/')
    try:
        os.makedirs(os.path.dirname(home_path))
    except Exception:
        pass

    # Start Schedulers
    from backgroundjobs import (background_settings_update,
                                background_specter_update,
                                background_scan_network,
                                background_specter_health,
                                background_mempool_seeker)

    def bk_su():
        with app.app_context():
            background_specter_update()

    def bk_stu():
        with app.app_context():
            background_settings_update()

    def bk_scan():
        with app.app_context():
            background_scan_network()

    def bk_specter_health():
        with app.app_context():
            background_specter_health()

    def bk_mempool_health():
        with app.app_context():
            background_mempool_seeker()

    app.scheduler = BackgroundScheduler()
    app.scheduler.add_job(bk_su, 'interval', seconds=1)
    app.scheduler.add_job(bk_stu, 'interval', seconds=1)
    app.scheduler.add_job(bk_scan, 'interval', seconds=1)
    app.scheduler.add_job(bk_specter_health, 'interval', seconds=1)
    app.scheduler.add_job(bk_mempool_health, 'interval', seconds=1)
    app.scheduler.start()
    print(success("✅ Background jobs running"))
    print("")
    app.app_context().push()

    print(success("✅ Application startup is complete"))

    return app
示例#10
0
def check_cryptocompare():
    from utils import pickle_it

    with yaspin(text="Testing price grab from Cryptocompare",
                color="green") as spinner:
        data = {'Response': 'Error', 'Message': None}
        try:
            api_key = pickle_it('load', 'cryptocompare_api.pkl')
            if api_key != 'file not found':
                baseURL = (
                    "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC"
                    + "&tsyms=USD&api_key=" + api_key)
                req = requests.get(baseURL)
                data = req.json()
                btc_price = (data['DISPLAY']['BTC']['USD']['PRICE'])
                spinner.text = (success(f"BTC price is: {btc_price}"))
                spinner.ok("✅ ")
                pickle_it('save', 'cryptocompare_api.pkl', api_key)
                return
            else:
                data = {'Response': 'Error', 'Message': 'No API Key is set'}
        except Exception as e:
            data = {'Response': 'Error', 'Message': str(e)}
            logging.error(data)

        try:
            if data['Response'] == 'Error':
                spinner.color = 'yellow'
                spinner.text = "CryptoCompare Returned an error " + data[
                    'Message']
                # ++++++++++++++++++++++++++
                #  Load Legacy
                # ++++++++++++++++++++++++++
                try:
                    # Let's try to use one of the
                    # legacy api keys stored under cryptocompare_api.keys file
                    # You can add as many as you'd like there
                    filename = 'warden/static/cryptocompare_api.keys'
                    file = open(filename, 'r')
                    for line in file:
                        legacy_key = str(line)

                        spinner.text = (
                            warning(f"Trying different API Keys..."))

                        baseURL = (
                            "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC"
                            + "&tsyms=USD&api_key=" + legacy_key)

                        try:
                            data = None
                            logging.debug(f"Trying API Key {legacy_key}")
                            request = requests.get(baseURL)
                            data = request.json()
                            btc_price = (
                                data['DISPLAY']['BTC']['USD']['PRICE'])
                            spinner.text = (
                                success(f"BTC price is: {btc_price}"))
                            spinner.ok("✅ ")
                            logging.debug(f"API Key {legacy_key} Success")
                            pickle_it('save', 'cryptocompare_api.pkl',
                                      legacy_key)
                            return
                        except Exception as e:
                            logging.debug(f"API Key {legacy_key} ERROR: {e}")
                            logging.debug(
                                f"API Key {legacy_key} Returned: {data}")
                            spinner.text = "Didn't work... Trying another."
                except Exception:
                    pass
                spinner.text = (error("Failed to get API Key - read below."))
                spinner.fail("[!]")
                print(
                    '    -----------------------------------------------------------------'
                )
                print(yellow("    Looks like you need to get an API Key. "))
                print(yellow("    The WARden comes with a shared key that"))
                print(yellow("    eventually gets to the call limit."))
                print(
                    '    -----------------------------------------------------------------'
                )
                print(
                    yellow(
                        '    Go to: https://www.cryptocompare.com/cryptopian/api-keys'
                    ))
                print(
                    yellow(
                        '    To get an API Key. Keys from cryptocompare are free.'
                    ))
                print(
                    yellow(
                        '    [Tip] Get a disposable email to signup and protect privacy.'
                    ))
                print(
                    yellow(
                        '    Services like https://temp-mail.org/en/ work well.'
                    ))

                print(muted("    Current API:"))
                print(f"    {api_key}")
                new_key = input('    Enter new API key (Q to quit): ')
                if new_key == 'Q' or new_key == 'q':
                    exit()
                pickle_it('save', 'cryptocompare_api.pkl', new_key)
                check_cryptocompare()
        except KeyError:
            try:
                btc_price = (data['DISPLAY']['BTC']['USD']['PRICE'])
                spinner.ok("✅ ")
                spinner.write(success(f"BTC price is: {btc_price}"))
                pickle_it('save', 'cryptocompare_api.pkl', api_key)
                return
            except Exception:
                spinner.text = (
                    warning("CryptoCompare Returned an UNKNOWN error"))
                spinner.fail("💥 ")
        return (data)
示例#11
0
def data_btc_price():
    from node_warden import launch_logger
    launch_logger()

    from node_warden import load_config
    config = load_config(quiet=True)
    fx_config = config['CURRENCIES']
    currencies = ast.literal_eval(fx_config.get('fx_list'))
    primary_fx = ast.literal_eval(fx_config.get('primary_fx'))
    price_data = multiple_price_grab('BTC', ','.join(currencies))
    # Get prices in different currencies
    tabs = []
    btc_usd_price = 0
    for fx in currencies:
        try:
            price_str = price_data['DISPLAY']['BTC'][fx]['PRICE']
            chg_str = price_data['DISPLAY']['BTC'][fx]['CHANGEPCTDAY']
            high = price_data['DISPLAY']['BTC'][fx]['HIGHDAY']
            low = price_data['DISPLAY']['BTC'][fx]['LOWDAY']
            market = muted(price_data['DISPLAY']['BTC'][fx]['LASTMARKET'])
            try:
                chg = float(chg_str)
                if chg >= 0:
                    chg_str = success('+' + chg_str + '%')
                elif chg < 0:
                    chg_str = error(chg_str + '%')
            except Exception:
                chg_str = muted(chg_str + '%')

            if fx == 'USD':
                btc_usd_price = cleanfloat(price_str)

            if fx == primary_fx:
                fx = info(fx)
            tabs.append(
                [u'  ' + fx, price_str, chg_str, low + ' - ' + high, market])

        except Exception as e:
            tabs.append(['error: ' + str(e)])

    tabs = tabulate(
        tabs,
        headers=['Fiat', 'Price', '% change', '24h Range', 'Source'],
        colalign=["center", "right", "right", "center", "right"])

    # GBTC
    gbtc_config = config['STOCKS']
    try:
        if gbtc_config.getboolean('GBTC_enabled'):
            tabs += '\n\n'
            gbtc_url = 'https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=GBTC&apikey=DDC232JDH'
            gbtc_data = tor_request(gbtc_url).json()['Global Quote']
            gbtc_tabs = []
            GBTC_shares = gbtc_config.getfloat('gbtc_shares')
            fairvalue, premium = (GBTC_premium(float(gbtc_data['05. price']),
                                               btc_usd_price, GBTC_shares))

            if premium * 1 > 0:
                premium = success('+' + jformat(premium, 2, 0.01) + '%')
            elif premium * 1 < 0:
                premium = error(jformat(premium, 2, 0.01) + '%')

            fairvalue = jformat(fairvalue, 2)

            chg_str = gbtc_data['10. change percent']
            try:
                chg = cleanfloat(chg_str)
                if chg > 0:
                    chg_str = success('+' + jformat(chg, 2) + ' %')
                elif chg < 0:
                    chg_str = error(jformat(chg, 2) + ' %')
            except Exception:
                chg_str = muted(chg_str)

            gbtc_tabs.append([
                'GBTC', gbtc_data['05. price'], chg_str,
                gbtc_data['04. low'] + ' - ' + gbtc_data['03. high'], premium,
                fairvalue, gbtc_data['07. latest trading day']
            ])
            gbtc_tabs = tabulate(gbtc_tabs,
                                 headers=[
                                     'Ticker', 'Price', '% change',
                                     '24h Range', 'Premium', 'Fair Value',
                                     'Last Update'
                                 ],
                                 colalign=[
                                     "center", "right", "right", "center",
                                     "right", "right", "right"
                                 ])
            tabs += gbtc_tabs

    except Exception as e:
        er_st = error(f' Error getting GBTC data: {e}')
        tabs += er_st

    tabs += (
        f"\n\n Last Refresh on: {info(datetime.now().strftime('%H:%M:%S'))}")
    return tabs
示例#12
0
def data_mempool():
    from node_warden import load_config
    from node_warden import launch_logger
    launch_logger()

    config = load_config(quiet=True)
    mp_config = config['MEMPOOL']
    url = mp_config.get('url')
    tabs = []

    # Get recommended fees

    mp_fee = tor_request(url + '/api/v1/fees/recommended').json()
    tabs = list(mp_fee.values())
    tabs = [[str(x) + ' sats/Vb' for x in tabs]]
    tabs = tabulate(tabs,
                    headers=["Fastest Fee", "30 min fee", "1 hour fee"],
                    colalign=["center", "center", "center"])
    try:
        block_height = tor_request(url + '/api/blocks/tip/height').json()
    except Exception:
        return (error(f' >> Error getting data from {url}. Retrying...'))
    # Save the latest block height
    saved_block = pickle_it(action='load', filename='block.pkl')
    if (saved_block != block_height) and (
            config['MEMPOOL'].getboolean('block_found_sound')):
        # Block found play sound
        try:
            engine = pyttsx3.init()
            engine.setProperty('rate', 270)
            engine.say(config['MEMPOOL'].get('block_found_txt'))
            engine.runAndWait()
        except Exception:
            pass
        logging.info(
            info('[MEMPOOL] ') +
            success("A new Bitcoin Block was just found. ") +
            yellow("'Tick. Tock. Next block.'"))

    pickle_it(action='save', filename='block.pkl', data=block_height)

    block_txt = success(f' Block Height: {jformat(block_height, 0)}\n\n')
    tabs = block_txt + info(' Mempool Fee Estimates: \n') + tabs

    try:
        mp_blocks = tor_request(url + '/api/blocks').json()
    except Exception:
        return (error(" >> Error getting Mempool data. Retrying..."))

    mp_tabs = []
    gradient_color = 0
    for block in mp_blocks:
        mp_tabs.append([
            time_ago(block['timestamp']),
            jformat(block['height'], 0),
            jformat(block['tx_count'], 0),
            jformat(block['size'], 2, 1000000) + ' MB'
        ])
        gradient_color += 1

    mp_tabs = tabulate(mp_tabs,
                       headers=[" Time", "Height", "Tx Count", "Size"],
                       colalign=["right", "center", "center", "right"])
    tabs += info('\n\n Latest Blocks: \n') + mp_tabs
    tabs += muted(f"\n\n Source: {url}  {success('[Tor Request]')}\n")
    return tabs
示例#13
0
def data_btc_price():
    from node_warden import launch_logger
    launch_logger()

    from node_warden import load_config
    config = load_config(quiet=True)
    fx_config = config['CURRENCIES']
    currencies = ast.literal_eval(fx_config.get('fx_list'))
    primary_fx = ast.literal_eval(fx_config.get('primary_fx'))
    price_data = multiple_price_grab('BTC', ','.join(currencies))
    # Get prices in different currencies
    tabs = []
    btc_usd_price = 0
    for fx in currencies:
        try:
            price_str = price_data['DISPLAY']['BTC'][fx]['PRICE']
            chg_str = price_data['DISPLAY']['BTC'][fx]['CHANGEPCTDAY']
            high = price_data['DISPLAY']['BTC'][fx]['HIGHDAY']
            low = price_data['DISPLAY']['BTC'][fx]['LOWDAY']
            market = muted(price_data['DISPLAY']['BTC'][fx]['LASTMARKET'])
            try:
                chg = float(chg_str)
                if chg >= 0:
                    chg_str = success('+' + chg_str + '%')
                elif chg < 0:
                    chg_str = error(chg_str + '%')
            except Exception:
                chg_str = muted(chg_str + '%')

            if fx == 'USD':
                btc_usd_price = cleanfloat(price_str)

            if fx == primary_fx:
                fx = info(fx)
            tabs.append(
                [u'  ' + fx, price_str, chg_str, low + ' - ' + high, market])

        except Exception as e:
            tabs.append(['error: ' + str(e)])

    if tabs == []:
        return (
            error(f' >> Error getting data from CryptoCompare. Retrying...'))

    try:
        tabs = tabulate(
            tabs,
            headers=['Fiat', 'Price', '% change', '24h Range', 'Source'],
            colalign=["center", "right", "right", "center", "right"])
    except Exception:
        return (
            error(f' >> Error getting data from CryptoCompare. Retrying...'))

    # GBTC
    gbtc_config = config['STOCKS']
    try:
        if gbtc_config.getboolean('GBTC_enabled'):
            tabs += '\n\n'
            gbtc_url = 'https://finnhub.io/api/v1/quote?symbol=GBTC&token=bvfhuqv48v6rhdtvnks0'
            gbtc_data = tor_request(gbtc_url).json()
            gbtc_tabs = []
            GBTC_shares = gbtc_config.getfloat('gbtc_shares')
            fairvalue, premium = (GBTC_premium((gbtc_data['c']), btc_usd_price,
                                               GBTC_shares))

            if premium * 1 > 0:
                premium = success('+' + jformat(premium, 2, 0.01) + '%')
            elif premium * 1 < 0:
                premium = error(jformat(premium, 2, 0.01) + '%')

            fairvalue = jformat(fairvalue, 2)

            chg_str = gbtc_data['c'] / gbtc_data['pc']
            try:
                chg = (chg_str)
                if chg > 0:
                    chg_str = success('+' + jformat(chg, 2) + ' %')
                elif chg < 0:
                    chg_str = error(jformat(chg, 2) + ' %')
            except Exception:
                chg_str = muted(chg_str)

            gbtc_tabs.append([
                'GBTC',
                jformat(gbtc_data['c'], 2), chg_str,
                jformat(gbtc_data['l'], 2) + ' - ' +
                jformat(gbtc_data['h'], 2), premium, fairvalue,
                time_ago(gbtc_data['t'])
            ])
            gbtc_tabs = tabulate(gbtc_tabs,
                                 headers=[
                                     'Ticker', 'Price', '% change',
                                     '24h Range', 'Premium', 'Fair Value',
                                     'Last Update'
                                 ],
                                 colalign=[
                                     "center", "right", "right", "center",
                                     "right", "right", "right"
                                 ])
            tabs += gbtc_tabs

    except Exception as e:
        er_st = error(f' Error getting GBTC data: {e}')
        tabs += er_st

    tabs += (
        f"\n\n Last Refresh on: {info(datetime.now().strftime('%H:%M:%S'))}")
    return tabs