holgern/beem

View on GitHub
beem/account.py

Summary

Maintainability
F
1 mo
Test Coverage
B
82%
# -*- coding: utf-8 -*-
import pytz
import json
from datetime import datetime, timedelta, date, time
import math
import random
import logging
from prettytable import PrettyTable
from beem.instance import shared_blockchain_instance
from .exceptions import AccountDoesNotExistsException, OfflineHasNoRPCException
from beemapi.exceptions import ApiNotSupported, MissingRequiredActiveAuthority, SupportedByHivemind, FilteredItemNotFound
from .blockchainobject import BlockchainObject
from .blockchain import Blockchain
from .utils import formatTimeString, formatTimedelta, remove_from_dict, reputation_to_score, addTzInfo
from beem.amount import Amount
from beembase import operations
from beem.rc import RC
from beemgraphenebase.account import PrivateKey, PublicKey, PasswordKey
from beemgraphenebase.py23 import bytes_types, integer_types, string_types, text_type
from beem.constants import STEEM_VOTE_REGENERATION_SECONDS, STEEM_1_PERCENT, STEEM_100_PERCENT, STEEM_VOTING_MANA_REGENERATION_SECONDS
log = logging.getLogger(__name__)


def extract_account_name(account):
    if isinstance(account, str):
        return account
    elif isinstance(account, Account):
        return account["name"]
    elif isinstance(account, dict) and "name" in account:
        return account["name"]
    else:
        return ""


class Account(BlockchainObject):
    """ This class allows to easily access Account data

        :param str account: Name of the account
        :param Steem/Hive blockchain_instance: Hive or Steem
               instance
        :param bool lazy: Use lazy loading
        :param bool full: Obtain all account data including orders, positions,
               etc.
        :param Hive hive_instance: Hive instance
        :param Steem steem_instance: Steem instance
        :returns: Account data
        :rtype: dictionary
        :raises beem.exceptions.AccountDoesNotExistsException: if account
                does not exist

        Instances of this class are dictionaries that come with additional
        methods (see below) that allow dealing with an account and its
        corresponding functions.

        .. code-block:: python

            >>> from beem.account import Account
            >>> from beem import Hive
            >>> from beem.nodelist import NodeList
            >>> nodelist = NodeList()
            >>> nodelist.update_nodes()
            >>> stm = Hive(node=nodelist.get_hive_nodes())
            >>> account = Account("gtg", blockchain_instance=stm)
            >>> print(account)
            <Account gtg>
            >>> print(account.balances) # doctest: +SKIP

        .. note:: This class comes with its own caching function to reduce the
                  load on the API server. Instances of this class can be
                  refreshed with ``Account.refresh()``. The cache can be
                  cleared with ``Account.clear_cache()``

    """

    type_id = 2

    def __init__(
        self,
        account,
        full=True,
        lazy=False,
        blockchain_instance=None,
        **kwargs
    ):
        """Initialize an account

        :param str account: Name of the account
        :param Steem blockchain_instance: Steem
               instance
        :param bool lazy: Use lazy loading
        :param bool full: Obtain all account data including orders, positions,
               etc.
        """
        self.full = full
        self.lazy = lazy
        if blockchain_instance is None:
            if kwargs.get("steem_instance"):
                blockchain_instance = kwargs["steem_instance"]
            elif kwargs.get("hive_instance"):
                blockchain_instance = kwargs["hive_instance"]
        self.blockchain = blockchain_instance or shared_blockchain_instance()
        if isinstance(account, dict):
            account = self._parse_json_data(account)
        super(Account, self).__init__(
            account,
            lazy=lazy,
            full=full,
            id_item="name",
            blockchain_instance=blockchain_instance
        )

    def refresh(self):
        """ Refresh/Obtain an account's data from the API server
        """
        if not self.blockchain.is_connected():
            return
        self.blockchain.rpc.set_next_node_on_empty_reply(self.blockchain.rpc.get_use_appbase())
        if self.blockchain.rpc.get_use_appbase():
            account = self.blockchain.rpc.find_accounts({'accounts': [self.identifier]}, api="database")
        else:
            if self.full:
                account = self.blockchain.rpc.get_accounts(
                    [self.identifier], api="database")
            else:
                account = self.blockchain.rpc.lookup_account_names(
                    [self.identifier], api="database")
        if self.blockchain.rpc.get_use_appbase() and "accounts" in account:
            account = account["accounts"]
        if account and isinstance(account, list) and len(account) == 1:
            account = account[0]
        if not account:
            raise AccountDoesNotExistsException(self.identifier)
        account = self._parse_json_data(account)
        self.identifier = account["name"]
        # self.blockchain.refresh_data()

        super(Account, self).__init__(account, id_item="name", lazy=self.lazy, full=self.full, blockchain_instance=self.blockchain)

    def _parse_json_data(self, account):
        parse_int = [
            "sbd_seconds", "savings_sbd_seconds", "average_bandwidth", "lifetime_bandwidth",
            "lifetime_market_bandwidth", "reputation", "withdrawn", "to_withdraw",
            "hbd_seconds", "savings_hbd_seconds",
        ]
        for p in parse_int:
            if p in account and isinstance(account.get(p), string_types):
                account[p] = int(account.get(p, 0))
        if "proxied_vsf_votes" in account:
            proxied_vsf_votes = []
            for p_int in account["proxied_vsf_votes"]:
                if isinstance(p_int, string_types):
                    proxied_vsf_votes.append(int(p_int))
                else:
                    proxied_vsf_votes.append(p_int)
            account["proxied_vsf_votes"] = proxied_vsf_votes
        parse_times = [
            "last_owner_update", "last_account_update", "created", "last_owner_proved", "last_active_proved",
            "last_account_recovery", "last_vote_time", "sbd_seconds_last_update", "sbd_last_interest_payment",
            "savings_sbd_seconds_last_update", "savings_sbd_last_interest_payment", "next_vesting_withdrawal",
            "last_market_bandwidth_update", "last_post", "last_root_post", "last_bandwidth_update",
            "hbd_seconds_last_update", "hbd_last_interest_payment", "savings_hbd_seconds_last_update",
            "savings_hbd_last_interest_payment"            
        ]
        for p in parse_times:
            if p in account and isinstance(account.get(p), string_types):
                account[p] = formatTimeString(account.get(p, "1970-01-01T00:00:00"))
        # Parse Amounts
        amounts = [
            "balance",
            "savings_balance",
            "sbd_balance",
            "savings_sbd_balance",
            "reward_sbd_balance",
            "hbd_balance",
            "savings_hbd_balance",
            "reward_hbd_balance",            
            "reward_steem_balance",
            "reward_hive_balance",
            "reward_vesting_balance",
            "reward_vesting_steem",
            "vesting_shares",
            "delegated_vesting_shares",
            "received_vesting_shares",
            "vesting_withdraw_rate",
            "vesting_balance",
        ]
        for p in amounts:
            if p in account and isinstance(account.get(p), (string_types, list, dict)):
                account[p] = Amount(account[p], blockchain_instance=self.blockchain)
        return account

    def json(self):
        output = self.copy()
        parse_int = [
            "sbd_seconds", "savings_sbd_seconds", "hbd_seconds", "savings_hbd_seconds",
        ]
        parse_int_without_zero = [
            "withdrawn", "to_withdraw", "lifetime_bandwidth", 'average_bandwidth',
        ]
        for p in parse_int:
            if p in output and isinstance(output[p], integer_types):
                output[p] = str(output[p])
        for p in parse_int_without_zero:
            if p in output and isinstance(output[p], integer_types) and output[p] != 0:
                output[p] = str(output[p])
        if "proxied_vsf_votes" in output:
            proxied_vsf_votes = []
            for p_int in output["proxied_vsf_votes"]:
                if isinstance(p_int, integer_types) and p_int != 0:
                    proxied_vsf_votes.append(str(p_int))
                else:
                    proxied_vsf_votes.append(p_int)
            output["proxied_vsf_votes"] = proxied_vsf_votes
        parse_times = [
            "last_owner_update", "last_account_update", "created", "last_owner_proved", "last_active_proved",
            "last_account_recovery", "last_vote_time", "sbd_seconds_last_update", "sbd_last_interest_payment",
            "savings_sbd_seconds_last_update", "savings_sbd_last_interest_payment", "next_vesting_withdrawal",
            "last_market_bandwidth_update", "last_post", "last_root_post", "last_bandwidth_update",
            "hbd_seconds_last_update", "hbd_last_interest_payment", "savings_hbd_seconds_last_update",
            "savings_hbd_last_interest_payment"            
        ]
        for p in parse_times:
            if p in output:
                p_date = output.get(p, datetime(1970, 1, 1, 0, 0))
                if isinstance(p_date, (datetime, date, time)):
                    output[p] = formatTimeString(p_date)
                else:
                    output[p] = p_date
        amounts = [
            "balance",
            "savings_balance",
            "sbd_balance",
            "savings_sbd_balance",
            "reward_sbd_balance",
            "reward_steem_balance",
            "hbd_balance",
            "savings_hbd_balance",
            "reward_hbd_balance",
            "reward_hive_balance",            
            "reward_vesting_balance",
            "reward_vesting_steem",
            "vesting_shares",
            "delegated_vesting_shares",
            "received_vesting_shares",
            "vesting_withdraw_rate",
            "vesting_balance",
        ]
        for p in amounts:
            if p in output:
                if p in output:
                    output[p] = output.get(p).json()
        return json.loads(str(json.dumps(output)))

    def getSimilarAccountNames(self, limit=5):
        """Deprecated, please use get_similar_account_names"""
        return self.get_similar_account_names(limit=limit)

    def get_rc(self):
        """Return RC of account"""
        b = Blockchain(blockchain_instance=self.blockchain)
        return b.find_rc_accounts(self["name"])

    def get_rc_manabar(self):
        """Returns current_mana and max_mana for RC"""
        rc_param = self.get_rc()
        max_mana = int(rc_param["max_rc"])
        last_mana = int(rc_param["rc_manabar"]["current_mana"])
        last_update_time = rc_param["rc_manabar"]["last_update_time"]
        last_update = datetime.utcfromtimestamp(last_update_time)
        diff_in_seconds = (datetime.utcnow() - last_update).total_seconds()
        current_mana = int(last_mana + diff_in_seconds * max_mana / STEEM_VOTING_MANA_REGENERATION_SECONDS)
        if current_mana > max_mana:
            current_mana = max_mana
        if max_mana > 0:
            current_pct = current_mana / max_mana * 100
        else:
            current_pct = 0
        max_rc_creation_adjustment = Amount(rc_param["max_rc_creation_adjustment"], blockchain_instance=self.blockchain)
        return {"last_mana": last_mana, "last_update_time": last_update_time, "current_mana": current_mana,
                "max_mana": max_mana, "current_pct": current_pct, "max_rc_creation_adjustment": max_rc_creation_adjustment}

    def get_similar_account_names(self, limit=5):
        """ Returns ``limit`` account names similar to the current account
            name as a list

            :param int limit: limits the number of accounts, which will be
                returned
            :returns: Similar account names as list
            :rtype: list

            This is a wrapper around :func:`beem.blockchain.Blockchain.get_similar_account_names()`
            using the current account name as reference.

        """
        b = Blockchain(blockchain_instance=self.blockchain)
        return b.get_similar_account_names(self.name, limit=limit)

    @property
    def name(self):
        """ Returns the account name
        """
        return self["name"]

    @property
    def profile(self):
        """ Returns the account profile
        """
        metadata = self.json_metadata
        if "profile" in metadata:
            return metadata["profile"]
        else:
            return {}

    @property
    def rep(self):
        """ Returns the account reputation
        """
        return self.get_reputation()

    @property
    def sp(self):
        """ Returns the accounts Steem Power
        """
        return self.get_token_power()

    @property
    def tp(self):
        """ Returns the accounts Hive/Steem Power
        """
        return self.get_token_power()

    @property
    def vp(self):
        """ Returns the account voting power in the range of 0-100%
        """
        return self.get_voting_power()

    @property
    def json_metadata(self):
        if self["json_metadata"] == '':
            return {}
        return json.loads(self["json_metadata"])

    @property
    def posting_json_metadata(self):
        if self["posting_json_metadata"] == '':
            return {}
        return json.loads(self["posting_json_metadata"])

    def print_info(self, force_refresh=False, return_str=False, use_table=False, **kwargs):
        """ Prints import information about the account
        """
        if force_refresh:
            self.refresh()
            self.blockchain.refresh_data(True)
        bandwidth = self.get_bandwidth()
        if bandwidth is not None and bandwidth["allocated"] is not None and bandwidth["allocated"] > 0:
            remaining = 100 - bandwidth["used"] / bandwidth["allocated"] * 100
            used_kb = bandwidth["used"] / 1024
            allocated_mb = bandwidth["allocated"] / 1024 / 1024
        last_vote_time_str = formatTimedelta(addTzInfo(datetime.utcnow()) - self["last_vote_time"])
        try:
            rc_mana = self.get_rc_manabar()
            rc = self.get_rc()
            rc_calc = RC(blockchain_instance=self.blockchain)
        except:
            rc_mana = None
            rc_calc = None

        if use_table:
            t = PrettyTable(["Key", "Value"])
            t.align = "l"
            t.add_row(["Name (rep)", self.name + " (%.2f)" % (self.rep)])
            t.add_row(["Voting Power", "%.2f %%, " % (self.get_voting_power())])
            t.add_row(["Downvoting Power", "%.2f %%, " % (self.get_downvoting_power())])
            t.add_row(["Vote Value", "%.2f $" % (self.get_voting_value_SBD())])
            t.add_row(["Last vote", "%s ago" % last_vote_time_str])
            t.add_row(["Full in ", "%s" % (self.get_recharge_time_str())])
            t.add_row(["Token Power", "%.2f %s" % (self.get_token_power(), self.blockchain.token_symbol)])
            t.add_row(["Balance", "%s, %s" % (str(self.balances["available"][0]), str(self.balances["available"][1]))])
            if False and bandwidth is not None and bandwidth["allocated"] is not None and bandwidth["allocated"] > 0:
                t.add_row(["Remaining Bandwidth", "%.2f %%" % (remaining)])
                t.add_row(["used/allocated Bandwidth", "(%.0f kb of %.0f mb)" % (used_kb, allocated_mb)])
            if rc_mana is not None:
                estimated_rc = int(rc["max_rc"]) * rc_mana["current_pct"] / 100
                t.add_row(["Remaining RC", "%.2f %%" % (rc_mana["current_pct"])])
                t.add_row(["Remaining RC", "(%.0f G RC of %.0f G RC)" % (estimated_rc / 10**9, int(rc["max_rc"]) / 10**9)])
                t.add_row(["Full in ", "%s" % (self.get_manabar_recharge_time_str(rc_mana))])
                t.add_row(["Est. RC for a comment", "%.2f G RC" % (rc_calc.comment() / 10**9)])
                t.add_row(["Est. RC for a vote", "%.2f G RC" % (rc_calc.vote() / 10**9)])
                t.add_row(["Est. RC for a transfer", "%.2f G RC" % (rc_calc.transfer() / 10**9)])
                t.add_row(["Est. RC for a custom_json", "%.2f G RC" % (rc_calc.custom_json() / 10**9)])

                t.add_row(["Comments with current RC", "%d comments" % (int(estimated_rc / rc_calc.comment()))])
                t.add_row(["Votes with current RC", "%d votes" % (int(estimated_rc / rc_calc.vote()))])
                t.add_row(["Transfer with current RC", "%d transfers" % (int(estimated_rc / rc_calc.transfer()))])
                t.add_row(["Custom_json with current RC", "%d transfers" % (int(estimated_rc / rc_calc.custom_json()))])

            if return_str:
                return t.get_string(**kwargs)
            else:
                print(t.get_string(**kwargs))
        else:
            ret = self.name + " (%.2f) \n" % (self.rep)
            ret += "--- Voting Power ---\n"
            ret += "%.2f %%, " % (self.get_voting_power())
            ret += " %.2f $\n" % (self.get_voting_value_SBD())
            ret += "full in %s \n" % (self.get_recharge_time_str())
            ret += "--- Downvoting Power ---\n"
            ret += "%.2f %% \n" % (self.get_downvoting_power())
            ret += "--- Balance ---\n"
            ret += "%.2f SP, " % (self.get_token_power())
            ret += "%s, %s\n" % (str(self.balances["available"][0]), str(self.balances["available"][1]))
            if False and bandwidth["allocated"] > 0:
                ret += "--- Bandwidth ---\n"
                ret += "Remaining: %.2f %%" % (remaining)
                ret += " (%.0f kb of %.0f mb)\n" % (used_kb, allocated_mb)
            if rc_mana is not None:
                estimated_rc = int(rc["max_rc"]) * rc_mana["current_pct"] / 100
                ret += "--- RC manabar ---\n"
                ret += "Remaining: %.2f %%" % (rc_mana["current_pct"])
                ret += " (%.0f G RC of %.0f G RC)\n" % (estimated_rc / 10**9, int(rc["max_rc"]) / 10**9)
                ret += "full in %s\n" % (self.get_manabar_recharge_time_str(rc_mana))
                ret += "--- Approx Costs ---\n"
                ret += "comment - %.2f G RC - enough RC for %d comments\n" % (rc_calc.comment() / 10**9, int(estimated_rc / rc_calc.comment()))
                ret += "vote - %.2f G RC - enough RC for %d votes\n" % (rc_calc.vote() / 10**9, int(estimated_rc / rc_calc.vote()))
                ret += "transfer - %.2f G RC - enough RC for %d transfers\n" % (rc_calc.transfer() / 10**9, int(estimated_rc / rc_calc.transfer()))
                ret += "custom_json - %.2f G RC - enough RC for %d custom_json\n" % (rc_calc.custom_json() / 10**9, int(estimated_rc / rc_calc.custom_json()))
            if return_str:
                return ret
            print(ret)

    def get_reputation(self):
        """ Returns the account reputation in the (steemit) normalized form
        """
        if not self.blockchain.is_connected():
            return None
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase():
            try:
                rep = self.blockchain.rpc.get_account_reputations({'account_lower_bound': self["name"], 'limit': 1}, api="reputation")['reputations']
                if len(rep) > 0:
                    rep = int(rep[0]['reputation'])
            except:
                if "reputation" in self:
                    rep = int(self['reputation'])
                else:
                    rep = 0
        else:
            rep = int(self['reputation'])
        return reputation_to_score(rep)

    def get_manabar(self):
        """ Return manabar
        """
        max_mana = self.get_effective_vesting_shares()
        if max_mana == 0:
            props = self.blockchain.get_chain_properties()
            required_fee_token = Amount(props["account_creation_fee"], blockchain_instance=self.blockchain)
            max_mana = int(self.blockchain.token_power_to_vests(required_fee_token))

        last_mana = int(self["voting_manabar"]["current_mana"])
        last_update_time = self["voting_manabar"]["last_update_time"]
        last_update = datetime.utcfromtimestamp(last_update_time)
        diff_in_seconds = (addTzInfo(datetime.utcnow()) - addTzInfo(last_update)).total_seconds()
        current_mana = int(last_mana + diff_in_seconds * max_mana / STEEM_VOTING_MANA_REGENERATION_SECONDS)
        if current_mana > max_mana:
            current_mana = max_mana
        if max_mana > 0:
            current_mana_pct = current_mana / max_mana * 100
        else:
            current_mana_pct = 0
        return {"last_mana": last_mana, "last_update_time": last_update_time,
                "current_mana": current_mana, "max_mana": max_mana, "current_mana_pct": current_mana_pct}

    def get_downvote_manabar(self):
        """ Return downvote manabar
        """
        if "downvote_manabar" not in self:
            return None
        max_mana = self.get_effective_vesting_shares() / 4
        if max_mana == 0:
            props = self.blockchain.get_chain_properties()
            required_fee_token = Amount(props["account_creation_fee"], blockchain_instance=self.blockchain)
            max_mana = int(self.blockchain.token_power_to_vests(required_fee_token) / 4)
              
        last_mana = int(self["downvote_manabar"]["current_mana"])
        last_update_time = self["downvote_manabar"]["last_update_time"]
        last_update = datetime.utcfromtimestamp(last_update_time)
        diff_in_seconds = (addTzInfo(datetime.utcnow()) - addTzInfo(last_update)).total_seconds()
        current_mana = int(last_mana + diff_in_seconds * max_mana / STEEM_VOTING_MANA_REGENERATION_SECONDS)
        if current_mana > max_mana:
            current_mana = max_mana
        if max_mana > 0:
            current_mana_pct = current_mana / max_mana * 100
        else:
            current_mana_pct = 0
        return {"last_mana": last_mana, "last_update_time": last_update_time,
                "current_mana": current_mana, "max_mana": max_mana, "current_mana_pct": current_mana_pct}

    def get_voting_power(self, with_regeneration=True):
        """ Returns the account voting power in the range of 0-100%

            :param bool with_regeneration: When True, voting power regeneration is
                included into the result (default True)
        """
        if "voting_manabar" in self:
            manabar = self.get_manabar()
            if with_regeneration:
                total_vp = manabar["current_mana_pct"]
            else:
                if manabar["max_mana"] > 0:
                    total_vp = manabar["last_mana"] / manabar["max_mana"] * 100
                else:
                    total_vp = 0
        elif "voting_power" in self:
            if with_regeneration:
                last_vote_time = self["last_vote_time"]
                diff_in_seconds = (addTzInfo(datetime.utcnow()) - (last_vote_time)).total_seconds()
                regenerated_vp = diff_in_seconds * STEEM_100_PERCENT / STEEM_VOTE_REGENERATION_SECONDS / 100
            else:
                regenerated_vp = 0
            total_vp = (self["voting_power"] / 100 + regenerated_vp)
        if total_vp > 100:
            return 100
        if total_vp < 0:
            return 0
        return total_vp

    def get_downvoting_power(self, with_regeneration=True):
        """ Returns the account downvoting power in the range of 0-100%

            :param bool with_regeneration: When True, downvoting power regeneration is
                included into the result (default True)
        """
        if "downvote_manabar" not in self:
            return 0

        manabar = self.get_downvote_manabar()
        if with_regeneration:
            total_down_vp = manabar["current_mana_pct"]
        else:
            if manabar["max_mana"] > 0:
                total_down_vp = manabar["last_mana"] / manabar["max_mana"] * 100
            else:
                total_down_vp = 0
        if total_down_vp > 100:
            return 100
        if total_down_vp < 0:
            return 0
        return total_down_vp

    def get_vests(self, only_own_vests=False):
        """ Returns the account vests

            :param bool only_own_vests: When True, only owned vests is
                returned without delegation (default False)
        """
        vests = (self["vesting_shares"])
        if not only_own_vests and "delegated_vesting_shares" in self and "received_vesting_shares" in self:
            vests = vests - (self["delegated_vesting_shares"]) + (self["received_vesting_shares"])

        return vests

    def get_effective_vesting_shares(self):
        """Returns the effective vesting shares"""
        vesting_shares = int(self["vesting_shares"])
        if "delegated_vesting_shares" in self and "received_vesting_shares" in self:
            vesting_shares = vesting_shares - int(self["delegated_vesting_shares"]) + int(self["received_vesting_shares"])
        timestamp = (self["next_vesting_withdrawal"] - addTzInfo(datetime(1970, 1, 1))).total_seconds()
        if timestamp > 0 and "vesting_withdraw_rate" in self and "to_withdraw" in self and "withdrawn" in self:
            vesting_shares -= min(int(self["vesting_withdraw_rate"]), int(self["to_withdraw"]) - int(self["withdrawn"]))
        return vesting_shares

    def get_token_power(self, only_own_vests=False, use_stored_data=True):
        """ Returns the account Hive/Steem power (amount of staked token + delegations)

            :param bool only_own_vests: When True, only owned vests is
                returned without delegation (default False)
            :param bool use_stored_data: When False, an API call returns the current
                vests_to_token_power ratio everytime (default True)

        """
        return self.blockchain.vests_to_token_power(self.get_vests(only_own_vests=only_own_vests), use_stored_data=use_stored_data)

    def get_steem_power(self, onlyOwnSP=False):
        """ Returns the account steem power
        """
        return self.get_token_power(only_own_vests=onlyOwnSP)

    def get_voting_value(self, post_rshares=0, voting_weight=100, voting_power=None, token_power=None, not_broadcasted_vote=True):
        """ Returns the account voting value in Hive/Steem token units
        """
        if voting_power is None:
            voting_power = self.get_voting_power()
        if token_power is None:
            tp = self.get_token_power()
        else:
            tp = token_power
        voteValue = self.blockchain.token_power_to_token_backed_dollar(tp, post_rshares=post_rshares, voting_power=voting_power * 100, vote_pct=voting_weight * 100, not_broadcasted_vote=not_broadcasted_vote)
        return voteValue

    def get_voting_value_SBD(self, post_rshares=0, voting_weight=100, voting_power=None, steem_power=None, not_broadcasted_vote=True):
        """ Returns the account voting value in SBD
        """
        return self.get_voting_value(post_rshares=post_rshares, voting_weight=voting_weight, voting_power=voting_power,
                                     token_power=steem_power, not_broadcasted_vote=not_broadcasted_vote)

    def get_vote_pct_for_SBD(self, sbd, post_rshares=0, voting_power=None, steem_power=None, not_broadcasted_vote=True):
        """ Returns the voting percentage needed to have a vote worth a given number of SBD.

            If the returned number is bigger than 10000 or smaller than -10000,
            the given SBD value is too high for that account

            :param sbd: The amount of SBD in vote value
            :type sbd: str, int, amount.Amount

        """
        return self.get_vote_pct_for_vote_value(sbd, post_rshares=post_rshares, voting_power=voting_power, token_power=steem_power, not_broadcasted_vote=not_broadcasted_vote)

    def get_vote_pct_for_vote_value(self, token_units, post_rshares=0, voting_power=None, token_power=None, not_broadcasted_vote=True):
        """ Returns the voting percentage needed to have a vote worth a given number of Hive/Steem token units

            If the returned number is bigger than 10000 or smaller than -10000,
            the given SBD value is too high for that account

            :param token_units: The amount of HBD/SBD in vote value
            :type token_units: str, int, amount.Amount

        """
        if voting_power is None:
            voting_power = self.get_voting_power()
        if token_power is None:
            token_power = self.get_token_power()

        if isinstance(token_units, Amount):
            token_units = Amount(token_units, blockchain_instance=self.blockchain)
        elif isinstance(token_units, string_types):
            token_units = Amount(token_units, blockchain_instance=self.blockchain)
        else:
            token_units = Amount(token_units, self.blockchain.backed_token_symbol, blockchain_instance=self.blockchain)
        if token_units['symbol'] != self.blockchain.backed_token_symbol:
            raise AssertionError('Should input %s, not any other asset!' % self.blockchain.backed_token_symbol)
        from beem import Steem
        if isinstance(self.blockchain, Steem):
            vote_pct = self.blockchain.rshares_to_vote_pct(self.blockchain.sbd_to_rshares(token_units, not_broadcasted_vote=not_broadcasted_vote), post_rshares=post_rshares, voting_power=voting_power * 100, steem_power=token_power)
        else:
            vote_pct = self.blockchain.rshares_to_vote_pct(self.blockchain.hbd_to_rshares(token_units, not_broadcasted_vote=not_broadcasted_vote), post_rshares=post_rshares, voting_power=voting_power * 100, hive_power=token_power)
        return vote_pct

    def get_creator(self):
        """ Returns the account creator or `None` if the account was mined
        """
        if self['mined']:
            return None
        ops = list(self.get_account_history(1, 1))
        if not ops or 'creator' not in ops[-1]:
            return None
        return ops[-1]['creator']

    def get_recharge_time_str(self, voting_power_goal=100, starting_voting_power=None):
        """ Returns the account recharge time as string

            :param float voting_power_goal: voting power goal in percentage (default is 100)
            :param float starting_voting_power: returns recharge time if current voting power is
                the provided value.

        """
        remainingTime = self.get_recharge_timedelta(voting_power_goal=voting_power_goal, starting_voting_power=starting_voting_power)
        return formatTimedelta(remainingTime)

    def get_recharge_timedelta(self, voting_power_goal=100, starting_voting_power=None):
        """ Returns the account voting power recharge time as timedelta object

            :param float voting_power_goal: voting power goal in percentage (default is 100)
            :param float starting_voting_power: returns recharge time if current voting power is
                the provided value.

        """
        if starting_voting_power is None:
            missing_vp = voting_power_goal - self.get_voting_power()
        elif isinstance(starting_voting_power, int) or isinstance(starting_voting_power, float):
            missing_vp = voting_power_goal - starting_voting_power
        else:
            raise ValueError('starting_voting_power must be a number.')
        if missing_vp < 0:
            return 0
        recharge_seconds = missing_vp * 100 * STEEM_VOTING_MANA_REGENERATION_SECONDS / STEEM_100_PERCENT
        return timedelta(seconds=recharge_seconds)

    def get_recharge_time(self, voting_power_goal=100, starting_voting_power=None):
        """ Returns the account voting power recharge time in minutes

            :param float voting_power_goal: voting power goal in percentage (default is 100)
            :param float starting_voting_power: returns recharge time if current voting power is
                the provided value.

        """
        return addTzInfo(datetime.utcnow()) + self.get_recharge_timedelta(voting_power_goal, starting_voting_power)

    def get_manabar_recharge_time_str(self, manabar, recharge_pct_goal=100):
        """ Returns the account manabar recharge time as string

            :param dict manabar: manabar dict from get_manabar() or get_rc_manabar()
            :param float recharge_pct_goal: mana recovery goal in percentage (default is 100)

        """
        remainingTime = self.get_manabar_recharge_timedelta(manabar, recharge_pct_goal=recharge_pct_goal)
        return formatTimedelta(remainingTime)

    def get_manabar_recharge_timedelta(self, manabar, recharge_pct_goal=100):
        """ Returns the account mana recharge time as timedelta object

            :param dict manabar: manabar dict from get_manabar() or get_rc_manabar()
            :param float recharge_pct_goal: mana recovery goal in percentage (default is 100)

        """
        if "current_mana_pct" in manabar:
            missing_rc_pct = recharge_pct_goal - manabar["current_mana_pct"]
        else:
            missing_rc_pct = recharge_pct_goal - manabar["current_pct"]
        if missing_rc_pct < 0:
            return 0
        recharge_seconds = missing_rc_pct * 100 * STEEM_VOTING_MANA_REGENERATION_SECONDS / STEEM_100_PERCENT
        return timedelta(seconds=recharge_seconds)

    def get_manabar_recharge_time(self, manabar, recharge_pct_goal=100):
        """ Returns the account mana recharge time in minutes

            :param dict manabar: manabar dict from get_manabar() or get_rc_manabar()
            :param float recharge_pct_goal: mana recovery goal in percentage (default is 100)

        """
        return addTzInfo(datetime.utcnow()) + self.get_manabar_recharge_timedelta(manabar, recharge_pct_goal)

    def get_feed(self, start_entry_id=0, limit=100, raw_data=False, short_entries=False, account=None):
        """ Returns a list of items in an account’s feed

            :param int start_entry_id: default is 0
            :param int limit: default is 100
            :param bool raw_data: default is False
            :param bool short_entries: when set to True and raw_data is True, get_feed_entries is used istead of get_feed
            :param str account: When set, a different account name is used (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("steemit", blockchain_instance=stm)
                >>> account.get_feed(0, 1, raw_data=True)
                []

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            return None
        from beem.discussions import Discussions, Query
        d = Discussions(blockchain_instance=self.blockchain)
        if short_entries:
            truncate_body = 1
        else:
            truncate_body = 0
        q = Query(limit=limit, tag=account, truncate_body=truncate_body)
        return [
            c for c in d.get_discussions("feed", q, limit=limit, raw_data=raw_data)
        ]

    def get_feed_entries(self, start_entry_id=0, limit=100, raw_data=True,
                         account=None):
        """ Returns a list of entries in an account’s feed

            :param int start_entry_id: default is 0
            :param int limit: default is 100
            :param bool raw_data: default is False
            :param bool short_entries: when set to True and raw_data is True, get_feed_entries is used istead of get_feed
            :param str account: When set, a different account name is used (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("steemit", blockchain_instance=stm)
                >>> account.get_feed_entries(0, 1)
                []

        """
        return self.get_feed(start_entry_id=start_entry_id, limit=limit, raw_data=raw_data, short_entries=True, account=account)

    def get_blog_entries(self, start_entry_id=0, limit=100, raw_data=True,
                         account=None):
        """ Returns the list of blog entries for an account

            :param int start_entry_id: default is 0
            :param int limit: default is 100
            :param bool raw_data: default is False
            :param str account: When set, a different account name is used (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("steemit", blockchain_instance=stm)
                >>> entry = account.get_blog_entries(0, 1, raw_data=True)[0]
                >>> print("%s - %s - %s" % (entry["author"], entry["permlink"], entry["blog"]))
                steemit - firstpost - steemit

        """
        return self.get_blog(start_entry_id=start_entry_id, limit=limit, raw_data=raw_data, short_entries=True, account=account)

    def get_blog(self, start_entry_id=0, limit=100, raw_data=False, short_entries=False, account=None):
        """ Returns the list of blog entries for an account

            :param int start_entry_id: default is 0
            :param int limit: default is 100
            :param bool raw_data: default is False
            :param bool short_entries: when set to True and raw_data is True, get_blog_entries is used istead of get_blog
            :param str account: When set, a different account name is used (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("steemit", blockchain_instance=stm)
                >>> account.get_blog(0, 1)
                [<Comment @steemit/firstpost>]

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)

        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        success = True
        if self.blockchain.rpc.get_use_appbase():
            try:
                if raw_data and short_entries:
                    ret = self.blockchain.rpc.get_blog_entries({'account': account, 'start_entry_id': start_entry_id, 'limit': limit}, api='follow')
                    if isinstance(ret, dict) and "blog" in ret:
                        ret = ret["blog"]
                    return [
                        c for c in ret
                    ]
                elif raw_data:
                    ret = self.blockchain.rpc.get_blog({'account': account, 'start_entry_id': start_entry_id, 'limit': limit}, api='follow')
                    if isinstance(ret, dict) and "blog" in ret:
                        ret = ret["blog"]
                    return [
                        c for c in ret
                    ]
                elif not raw_data:
                    from .comment import Comment
                    ret = self.blockchain.rpc.get_blog({'account': account, 'start_entry_id': start_entry_id, 'limit': limit}, api='follow')
                    if isinstance(ret, dict) and "blog" in ret:
                        ret = ret["blog"]
                    return [
                        Comment(c["comment"], blockchain_instance=self.blockchain) for c in ret
                    ]
            except:
                success = False

        if not self.blockchain.rpc.get_use_appbase() or not success:
            if raw_data and short_entries:
                return [
                    c for c in self.blockchain.rpc.get_blog_entries(account, start_entry_id, limit, api='follow')
                ]

            elif raw_data:
                return [
                    c for c in self.blockchain.rpc.get_blog(account, start_entry_id, limit, api='follow')
                ]

            else:
                from .comment import Comment
                blog_list = self.blockchain.rpc.get_blog(account, start_entry_id, limit, api='follow')
                if blog_list is None:
                    return []
                return [
                    Comment(c["comment"], blockchain_instance=self.blockchain) for c in blog_list
                ]

    def get_notifications(self, only_unread=True, limit=100, raw_data=False, account=None):
        """ Returns account notifications

            :param bool only_unread: When True, only unread notfications are shown
            :param int limit: When set, the number of shown notifications is limited (max limit = 100)
            :param bool raw_data: When True, the raw data from the api call is returned.
            :param str account: (optional) the account for which the notification should be received
                to (defaults to ``default_account``)
        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if only_unread:
            unread_notes = self.blockchain.rpc.unread_notifications({'account': account}, api='bridge')
            if limit is None or limit > unread_notes["unread"]:
                limit = unread_notes["unread"]
        if limit is None or limit == 0:
            return []
        if limit > 100:
            limit = 100
        notifications = self.blockchain.rpc.account_notifications({'account': account, 'limit': limit}, api='bridge')
        if raw_data:
            return notifications
        ret = []
        for note in notifications:
            note["date"] = formatTimeString(note["date"])
            ret.append(note)
        return ret

    def mark_notifications_as_read(self, last_read=None, account=None):
        """ Broadcast a mark all notification as read custom_json

            :param str last_read: When set, this datestring is used to set the mark as read date
            :param str account: (optional) the account to broadcast the custom_json
                to (defaults to ``default_account``)

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not account:
            raise ValueError("You need to provide an account")
        if last_read is None:
            last_notification = self.get_notifications(only_unread=False, limit=1, account=account)
            if len(last_notification) == 0:
                raise ValueError("Notification list is empty")
            last_read = last_notification[0]["date"]
        if isinstance(last_read, datetime):
            last_read = formatTimeString(last_read)
        json_body = [
            'setLastRead', {
                'date': last_read,
            }
        ]
        return self.blockchain.custom_json(
            "notify", json_body, required_posting_auths=[account])

    def get_blog_authors(self, account=None):
        """ Returns a list of authors that have had their content reblogged on a given blog account

            :param str account: When set, a different account name is used (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("gtg", blockchain_instance=stm)
                >>> account.get_blog_authors() # doctest: +SKIP

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase():
            try:
                return self.blockchain.rpc.get_blog_authors({'blog_account': account}, api='follow')['blog_authors']
            except:
                return self.blockchain.rpc.get_blog_authors(account, api='follow')
        else:
            return self.blockchain.rpc.get_blog_authors(account, api='follow')

    def get_follow_count(self, account=None):
        """ get_follow_count """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase():
            try:
                return self.blockchain.rpc.get_follow_count({'account': account}, api='follow')
            except:
                return self.blockchain.rpc.get_follow_count(account, api='condenser')
        else:
            return self.blockchain.rpc.get_follow_count(account, api='follow')

    def get_followers(self, raw_name_list=True, limit=100):
        """ Returns the account followers as list
        """
        name_list = [x['follower'] for x in self._get_followers(direction="follower", limit=limit)]
        if raw_name_list:
            return name_list
        else:
            return Accounts(name_list, blockchain_instance=self.blockchain)

    def get_following(self, raw_name_list=True, limit=100):
        """ Returns who the account is following as list
        """
        name_list = [x['following'] for x in self._get_followers(direction="following", limit=limit)]
        if raw_name_list:
            return name_list
        else:
            return Accounts(name_list, blockchain_instance=self.blockchain)

    def get_muters(self, raw_name_list=True, limit=100):
        """ Returns the account muters as list
        """
        name_list = [x['follower'] for x in self._get_followers(direction="follower", what="ignore", limit=limit)]
        if raw_name_list:
            return name_list
        else:
            return Accounts(name_list, blockchain_instance=self.blockchain)

    def get_mutings(self, raw_name_list=True, limit=100):
        """ Returns who the account is muting as list
        """
        name_list = [x['following'] for x in self._get_followers(direction="following", what="ignore", limit=limit)]
        if raw_name_list:
            return name_list
        else:
            return Accounts(name_list, blockchain_instance=self.blockchain)

    def get_follow_list(self, follow_type, starting_account=None, limit=100, raw_name_list=True):
        """ Returns the follow list for the specified follow_type (Only HIVE with HF >= 24)

            :param list follow_type: follow_type can be `blacklisted`, `follow_blacklist` `muted`, or `follow_muted`
        """
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        limit_reached = True
        cnt = 0
        while limit_reached:
            self.blockchain.rpc.set_next_node_on_empty_reply(False)
            query = {'observer': self.name, 'follow_type': follow_type, 'starting_account': starting_account, 'limit': limit}
            followers = self.blockchain.rpc.get_follow_list(query, api='bridge')
            if cnt == 0:
                name_list = followers
            elif followers is not None and len(followers) > 1:
                name_list += followers[1:]
            if followers is not None and len(followers) >= limit:
                starting_account = followers[-1]
                limit_reached = True
                cnt += 1
            else:
                limit_reached = False
        if raw_name_list:
            return name_list
        else:
            return Accounts(name_list, blockchain_instance=self.blockchain)

    def _get_followers(self, direction="follower", last_user="", what="blog", limit=100):
        """ Help function, used in get_followers and get_following
        """
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        followers_list = []
        limit_reached = True
        cnt = 0
        while limit_reached:
            self.blockchain.rpc.set_next_node_on_empty_reply(False)
            if self.blockchain.rpc.get_use_appbase():
                query = {'account': self.name, 'start': last_user, 'type': what, 'limit': limit}
                if direction == "follower":
                    try:
                        followers = self.blockchain.rpc.get_followers(query, api='follow')
                    except:
                        followers = self.blockchain.rpc.get_followers(self.name, last_user, what, limit, api='condenser')
                    if isinstance(followers, dict) and 'followers' in followers:
                        followers = followers['followers']
                elif direction == "following":
                    try:
                        followers = self.blockchain.rpc.get_following(query, api='follow')
                    except:
                        followers = self.blockchain.rpc.get_following(self.name, last_user, what, limit, api='condenser')
                    if isinstance(followers, dict) and 'following' in followers:
                        followers = followers['following']
            else:
                if direction == "follower":
                    try:
                        followers = self.blockchain.rpc.get_followers(self.name, last_user, what, limit, api='follow')
                    except:
                        followers = self.blockchain.rpc.get_followers([self.name, last_user, what, limit], api='condenser')
                elif direction == "following":
                    try:
                        followers = self.blockchain.rpc.get_following(self.name, last_user, what, limit, api='follow')
                    except:
                        followers = self.blockchain.rpc.get_following(self.name, last_user, what, limit, api='condenser')
            if cnt == 0:
                followers_list = followers
            elif followers is not None and len(followers) > 1:
                followers_list += followers[1:]
            if followers is not None and len(followers) >= limit:
                last_user = followers[-1][direction]
                limit_reached = True
                cnt += 1
            else:
                limit_reached = False

        return followers_list

    def list_all_subscriptions(self, account=None):
        """Returns all subscriptions"""
        if account is None:
            account = self["name"]
        account = extract_account_name(account)        
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(True)
        return self.blockchain.rpc.list_all_subscriptions({'account': account}, api='bridge')

    def get_account_posts(self, sort="feed", limit=20, account=None, observer=None, raw_data=False):
        """Returns account feed"""
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if observer is None:
            observer = account
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        from beem.comment import AccountPosts
        return AccountPosts(sort, account, observer=observer, limit=limit, raw_data=raw_data)

    @property
    def available_balances(self):
        """ List balances of an account. This call returns instances of
            :class:`beem.amount.Amount`.
        """
        if "sbd_balance" in self:
            amount_list = ["balance", "sbd_balance", "vesting_shares"]
        elif "hbd_balance" in self:
            amount_list = ["balance", "hbd_balance", "vesting_shares"]
        else:
            amount_list = ["balance", "vesting_shares"]
        available_amount = []
        for amount in amount_list:
            if amount in self:
                available_amount.append(self[amount].copy())
        return available_amount

    @property
    def saving_balances(self):
        savings_amount = []
        if "savings_sbd_balance" in self:
            amount_list = ["savings_balance", "savings_sbd_balance"]
        elif "savings_hbd_balance" in self:
            amount_list = ["savings_balance", "savings_hbd_balance"]
        else:
            amount_list = ["savings_balance"]
        for amount in amount_list:
            if amount in self:
                savings_amount.append(self[amount].copy())
        return savings_amount

    @property
    def reward_balances(self):
        if "reward_steem_balance" in self and "reward_sbd_balance" in self:
            amount_list = ["reward_steem_balance", "reward_sbd_balance", "reward_vesting_balance"]
        elif "reward_hive_balance" in self and "reward_hbd_balance" in self:
            amount_list = ["reward_hive_balance", "reward_hbd_balance", "reward_vesting_balance"]
        else:
            amount_list = []
        rewards_amount = []
        for amount in amount_list:
            if amount in self:
                rewards_amount.append(self[amount].copy())
        return rewards_amount

    @property
    def total_balances(self):
        symbols = []
        for balance in self.available_balances:
            symbols.append(balance["symbol"])
        ret = []
        for i in range(len(symbols)):
            balance_sum = self.get_balance(self.available_balances, symbols[i])
            balance_sum = balance_sum + self.get_balance(self.saving_balances, symbols[i])
            balance_sum = balance_sum + self.get_balance(self.reward_balances, symbols[i])
            ret.append(balance_sum)
        return ret

    @property
    def balances(self):
        """ Returns all account balances as dictionary
        """
        return self.get_balances()

    def get_balances(self):
        """ Returns all account balances as dictionary

            :returns: Account balances
            :rtype: dictionary

            Sample output:

                .. code-block:: js

                    {
                        'available': [102.985 STEEM, 0.008 SBD, 146273.695970 VESTS],
                        'savings': [0.000 STEEM, 0.000 SBD],
                        'rewards': [0.000 STEEM, 0.000 SBD, 0.000000 VESTS],
                        'total': [102.985 STEEM, 0.008 SBD, 146273.695970 VESTS]
                    }

        """
        return {
            'available': self.available_balances,
            'savings': self.saving_balances,
            'rewards': self.reward_balances,
            'total': self.total_balances,
        }

    def get_balance(self, balances, symbol):
        """ Obtain the balance of a specific Asset. This call returns instances of
            :class:`beem.amount.Amount`. Available balance types:

            * "available"
            * "saving"
            * "reward"
            * "total"

            :param str balances: Defines the balance type
            :param symbol: Can be "SBD", "STEEM" or "VESTS
            :type symbol: str, dict

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("beem.app", blockchain_instance=stm)
                >>> account.get_balance("rewards", "HBD")
                0.000 HBD

        """
        if isinstance(balances, string_types):
            if balances == "available":
                balances = self.available_balances
            elif balances == "savings":
                balances = self.saving_balances
            elif balances == "rewards":
                balances = self.reward_balances
            elif balances == "total":
                balances = self.total_balances
            else:
                return

        if isinstance(symbol, dict) and "symbol" in symbol:
            symbol = symbol["symbol"]

        for b in balances:
            if b["symbol"] == symbol:
                return b
        from .amount import Amount
        return Amount(0, symbol, blockchain_instance=self.blockchain)

    def interest(self):
        """ Calculate interest for an account

            :param str account: Account name to get interest for
            :rtype: dictionary

            Sample output:

            .. code-block:: js

                {
                    'interest': 0.0,
                    'last_payment': datetime.datetime(2018, 1, 26, 5, 50, 27, tzinfo=<UTC>),
                    'next_payment': datetime.datetime(2018, 2, 25, 5, 50, 27, tzinfo=<UTC>),
                    'next_payment_duration': datetime.timedelta(-65, 52132, 684026),
                    'interest_rate': 0.0
                }

        """
        interest_amount = 0
        interest_rate = 0
        next_payment = datetime(1970, 1, 1, 0, 0, 0)
        last_payment = datetime(1970, 1, 1, 0, 0, 0)
        if "sbd_last_interest_payment" in self:
            last_payment = (self["sbd_last_interest_payment"])
            next_payment = last_payment + timedelta(days=30)
            interest_rate = self.blockchain.get_dynamic_global_properties()[
                "sbd_interest_rate"] / 100  # percent
            interest_amount = (interest_rate / 100) * int(
                int(self["sbd_seconds"]) / (60 * 60 * 24 * 356)) * 10**-3
        elif "hbd_last_interest_payment" in self:
            last_payment = (self["hbd_last_interest_payment"])
            next_payment = last_payment + timedelta(days=30)
            interest_rate = self.blockchain.get_dynamic_global_properties()[
                "hbd_interest_rate"] / 100  # percent
            interest_amount = (interest_rate / 100) * int(
                int(self["hbd_seconds"]) / (60 * 60 * 24 * 356)) * 10**-3            
        return {
            "interest": interest_amount,
            "last_payment": last_payment,
            "next_payment": next_payment,
            "next_payment_duration": next_payment - addTzInfo(datetime.utcnow()),
            "interest_rate": interest_rate,
        }

    @property
    def is_fully_loaded(self):
        """ Is this instance fully loaded / e.g. all data available?

            :rtype: bool
        """
        return (self.full)

    def ensure_full(self):
        """Ensure that all data are loaded"""
        if not self.is_fully_loaded:
            self.full = True
            self.refresh()

    def get_account_bandwidth(self, bandwidth_type=1, account=None):
        """ get_account_bandwidth """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        return self.blockchain.rpc.get_account_bandwidth(account, bandwidth_type)

    def get_bandwidth(self):
        """ Returns used and allocated bandwidth

            :rtype: dictionary

            Sample output:

                .. code-block:: js

                    {
                        'used': 0,
                        'allocated': 2211037
                    }

        """
        account = self["name"]
        global_properties = self.blockchain.get_dynamic_global_properties()
        try:
            reserve_ratio = self.blockchain.get_reserve_ratio()
        except:
            return {"used": 0,
                    "allocated": 0}
        if "received_vesting_shares" in self:
            received_vesting_shares = self["received_vesting_shares"].amount
        else:
            received_vesting_shares = 0
        vesting_shares = self["vesting_shares"].amount
        if reserve_ratio is None or reserve_ratio["max_virtual_bandwidth"] is None:
            return {"used": None,
                    "allocated": None}
        max_virtual_bandwidth = float(reserve_ratio["max_virtual_bandwidth"])
        total_vesting_shares = Amount(global_properties["total_vesting_shares"], blockchain_instance=self.blockchain).amount
        allocated_bandwidth = (max_virtual_bandwidth * (vesting_shares + received_vesting_shares) / total_vesting_shares)
        allocated_bandwidth = round(allocated_bandwidth / 1000000)

        if self.blockchain.is_connected() and self.blockchain.rpc.get_use_appbase():
            try:
                account_bandwidth = self.get_account_bandwidth(bandwidth_type=1, account=account)
            except:
                account_bandwidth = None
            if account_bandwidth is None:
                return {"used": 0,
                        "allocated": allocated_bandwidth}
            last_bandwidth_update = formatTimeString(account_bandwidth["last_bandwidth_update"])
            average_bandwidth = float(account_bandwidth["average_bandwidth"])
        else:
            last_bandwidth_update = (self["last_bandwidth_update"])
            average_bandwidth = float(self["average_bandwidth"])
        total_seconds = 604800

        seconds_since_last_update = addTzInfo(datetime.utcnow()) - last_bandwidth_update
        seconds_since_last_update = seconds_since_last_update.total_seconds()
        used_bandwidth = 0
        if seconds_since_last_update < total_seconds:
            used_bandwidth = (((total_seconds - seconds_since_last_update) * average_bandwidth) / total_seconds)
        used_bandwidth = round(used_bandwidth / 1000000)

        return {"used": used_bandwidth,
                "allocated": allocated_bandwidth}
        # print("bandwidth percent used: " + str(100 * used_bandwidth / allocated_bandwidth))
        # print("bandwidth percent remaining: " + str(100 - (100 * used_bandwidth / allocated_bandwidth)))

    def get_owner_history(self, account=None):
        """ Returns the owner history of an account.

            :param str account: When set, a different account is used for the request (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("beem.app", blockchain_instance=stm)
                >>> account.get_owner_history()
                []

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase():
            return self.blockchain.rpc.find_owner_histories({'owner': account}, api="database")['owner_auths']
        else:
            return self.blockchain.rpc.get_owner_history(account)

    def get_conversion_requests(self, account=None):
        """ Returns a list of SBD conversion request

            :param str account: When set, a different account is used for the request (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("beem.app", blockchain_instance=stm)
                >>> account.get_conversion_requests()
                []

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase() and "sbd_balance" in self:
            return self.blockchain.rpc.find_sbd_conversion_requests({'account': account}, api="database")['requests']
        elif self.blockchain.rpc.get_use_appbase() and "hbd_balance" in self:
            return self.blockchain.rpc.find_hbd_conversion_requests({'account': account}, api="database")['requests']
        else:
            return self.blockchain.rpc.get_conversion_requests(account)

    def get_vesting_delegations(self, start_account="", limit=100, account=None):
        """ Returns the vesting delegations by an account.

            :param str account: When set, a different account is used for the request (Default is object account name)
            :param str start_account: delegatee to start with, leave empty to start from the first by name
            :param int limit: maximum number of results to return
            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("beem.app", blockchain_instance=stm)
                >>> account.get_vesting_delegations()
                []

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase():
            delegations = self.blockchain.rpc.list_vesting_delegations(
                {'start': [account, start_account], 'limit': limit,
                 'order': 'by_delegation'}, api="database")['delegations']
            return [d for d in delegations if d['delegator'] == account]
        else:
            return self.blockchain.rpc.get_vesting_delegations(account, start_account, limit)

    def get_withdraw_routes(self, account=None):
        """ Returns the withdraw routes for an account.

            :param str account: When set, a different account is used for the request (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("beem.app", blockchain_instance=stm)
                >>> account.get_withdraw_routes()
                []

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase():
            return self.blockchain.rpc.find_withdraw_vesting_routes({'account': account, 'order': 'by_withdraw_route'}, api="database")['routes']
        else:
            return self.blockchain.rpc.get_withdraw_routes(account, 'all')

    def get_savings_withdrawals(self, direction="from", account=None):
        """ Returns the list of savings withdrawls for an account.

            :param str account: When set, a different account is used for the request (Default is object account name)
            :param str direction: Can be either from or to (only non appbase nodes)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("beem.app", blockchain_instance=stm)
                >>> account.get_savings_withdrawals()
                []

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase():
            return self.blockchain.rpc.find_savings_withdrawals({'account': account}, api="database")['withdrawals']
        elif direction == "from":
            return self.blockchain.rpc.get_savings_withdraw_from(account)
        elif direction == "to":
            return self.blockchain.rpc.get_savings_withdraw_to(account)

    def get_recovery_request(self, account=None):
        """ Returns the recovery request for an account

            :param str account: When set, a different account is used for the request (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("beem.app", blockchain_instance=stm)
                >>> account.get_recovery_request()
                []

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase():
            return self.blockchain.rpc.find_account_recovery_requests({'account': account}, api="database")['requests']
        else:
            return self.blockchain.rpc.get_recovery_request(account)

    def get_escrow(self, escrow_id=0, account=None):
        """ Returns the escrow for a certain account by id

            :param int escrow_id: Id (only pre appbase)
            :param str account: When set, a different account is used for the request (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("beem.app", blockchain_instance=stm)
                >>> account.get_escrow(1234)
                []

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase():
            return self.blockchain.rpc.find_escrows({'from': account}, api="database")['escrows']
        else:
            return self.blockchain.rpc.get_escrow(account, escrow_id)

    def verify_account_authority(self, keys, account=None):
        """ Returns true if the signers have enough authority to authorize an account.

            :param list keys: public key
            :param str account: When set, a different account is used for the request (Default is object account name)

            :rtype: dictionary

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("steemit", blockchain_instance=stm)
                >>> print(account.verify_account_authority(["STM7Q2rLBqzPzFeteQZewv9Lu3NLE69fZoLeL6YK59t7UmssCBNTU"])["valid"])
                False

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        if not isinstance(keys, list):
            keys = [keys]
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        try:
            if self.blockchain.rpc.get_use_appbase():
                return self.blockchain.rpc.verify_account_authority({'account': account, 'signers': keys}, api="database")
            else:
                return self.blockchain.rpc.verify_account_authority(account, keys)
        except MissingRequiredActiveAuthority:
            return {'valid': False}

    def get_tags_used_by_author(self, account=None):
        """ Returns a list of tags used by an author.

            :param str account: When set, a different account is used for the request (Default is object account name)

            :rtype: list

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if self.blockchain.rpc.get_use_appbase():
            return self.blockchain.rpc.get_tags_used_by_author({'author': account}, api="tags")['tags']
        else:
            return self.blockchain.rpc.get_tags_used_by_author(account, api="tags")

    def get_expiring_vesting_delegations(self, after=None, limit=1000, account=None):
        """ Returns the expirations for vesting delegations.

            :param datetime after: expiration after (only for pre appbase nodes)
            :param int limit: limits number of shown entries (only for pre appbase nodes)
            :param str account: When set, a different account is used for the request (Default is object account name)

            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("beem.app", blockchain_instance=stm)
                >>> account.get_expiring_vesting_delegations()
                []

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if after is None:
            after = addTzInfo(datetime.utcnow()) - timedelta(days=8)
        if self.blockchain.rpc.get_use_appbase():
            return self.blockchain.rpc.find_vesting_delegation_expirations({'account': account}, api="database")['delegations']
        else:
            return self.blockchain.rpc.get_expiring_vesting_delegations(account, formatTimeString(after), limit)

    def get_account_votes(self, account=None, start_author="", start_permlink="", limit=1000, start_date=None):
        """ Returns all votes that the account has done
            
            :rtype: list

            .. code-block:: python

                >>> from beem.account import Account
                >>> from beem import Hive
                >>> from beem.nodelist import NodeList
                >>> nodelist = NodeList()
                >>> nodelist.update_nodes()
                >>> stm = Hive(node=nodelist.get_hive_nodes())
                >>> account = Account("beem.app", blockchain_instance=stm)
                >>> account.get_account_votes()  # doctest: +SKIP

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        # self.blockchain.rpc.set_next_node_on_empty_reply(False)
        # if self.blockchain.rpc.get_use_appbase():
        #     vote_list = self.blockchain.rpc.get_account_votes(account, api="condenser")
        # else:
        #    vote_list = self.blockchain.rpc.get_account_votes(account)
        # if isinstance(vote_list, dict) and "error" in vote_list:
        self.blockchain.rpc.set_next_node_on_empty_reply(True)
        vote_list = []
        finished = False
        while not finished:
            try:
                ret = self.blockchain.rpc.list_votes({"start": [account, start_author, start_permlink], "limit": limit, "order": "by_voter_comment"}, api="database")["votes"]
            except SupportedByHivemind:
                return vote_list
            if len(ret) < limit:
                finished = True
            if start_author != "":
                if len(ret) == 0:
                    finished = True
                ret = ret[1:]
            for vote in ret:
                if vote["voter"] != account:
                    finished = True
                    continue
                last_update = formatTimeString(vote["last_update"])
                if start_date is not None and last_update < start_date:
                    finished = True
                    continue
                vote_list.append(vote)
                start_author = vote["author"]
                start_permlink = vote["permlink"]
        return vote_list
        # else:
        #     return vote_list

    def get_vote(self, comment):
        """Returns a vote if the account has already voted for comment.

            :param comment: can be a Comment object or a authorpermlink
            :type comment: str, Comment
        """
        from beem.comment import Comment
        c = Comment(comment, blockchain_instance=self.blockchain)
        for v in c["active_votes"]:
            if v["voter"] == self["name"]:
                return v
        return None

    def has_voted(self, comment):
        """Returns if the account has already voted for comment

            :param comment: can be a Comment object or a authorpermlink
            :type comment: str, Comment
        """
        from beem.comment import Comment
        c = Comment(comment, blockchain_instance=self.blockchain)
        active_votes = {v["voter"]: v for v in c["active_votes"]}
        return self["name"] in active_votes

    def virtual_op_count(self, until=None):
        """ Returns the number of individual account transactions

            :rtype: list
        """
        if until is not None:
            return self.estimate_virtual_op_num(until, stop_diff=1)
        else:
            try:
                op_count = 0
                op_count = self._get_account_history(start=-1, limit=1)
                if op_count is None or len(op_count) == 0:
                    op_count = self._get_account_history(start=-1, limit=1)
                if isinstance(op_count, list) and len(op_count) > 0 and len(op_count[0]) > 0:
                    if self.blockchain.rpc.url == "https://api.hive.blog":
                        return op_count[-1][0] + 1
                    return op_count[-1][0]
                else:
                    return 0
            except IndexError:
                return 0

    def _get_account_history(self, account=None, start=-1, limit=1, operation_filter_low=None, operation_filter_high=None):
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if limit < 1:
            limit = 1
        elif limit > 1000:
            limit = 1000
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        if operation_filter_low is None and operation_filter_high is None:
            if self.blockchain.rpc.get_use_appbase():
                try:
                    ret = self.blockchain.rpc.get_account_history({'account': account, 'start': start, 'limit': limit}, api="account_history")
                    if ret is not None:
                        ret = ret["history"]
                except ApiNotSupported:
                    ret = self.blockchain.rpc.get_account_history(account, start, limit, api="condenser")
            else:
                ret = self.blockchain.rpc.get_account_history(account, start, limit, api="database")
        else:
            if self.blockchain.rpc.get_use_appbase():
                try:
                    ret = self.blockchain.rpc.get_account_history({'account': account, 'start': start, 'limit': limit,
                                                                   'operation_filter_low': operation_filter_low,
                                                                   'operation_filter_high': operation_filter_high}, api="account_history")
                    if ret is not None:
                        ret = ret["history"]
                except ApiNotSupported:
                    ret = self.blockchain.rpc.get_account_history(account, start, limit,
                                                                  operation_filter_low,
                                                                  operation_filter_high, api="condenser")
            else:
                ret = self.blockchain.rpc.get_account_history(account, start, limit,
                                                              operation_filter_low,
                                                              operation_filter_high,
                                                              api="database")            
        return ret

    def _get_blocknum_from_hist(self, index, min_index=1):
        if index >= 0 and index < min_index:
            index = min_index
        op = self._get_account_history(start=(index))
        if len(op) == 0:
            return None
        return op[0][1]['block']

    def _get_first_blocknum(self):
        min_index = 0
        try:
            created = self._get_blocknum_from_hist(0, min_index=min_index)
        except:
            min_index = 1
            created = self._get_blocknum_from_hist(0, min_index=min_index)
        return created, min_index

    def estimate_virtual_op_num(self, blocktime, stop_diff=0, max_count=100, min_index=None):
        """ Returns an estimation of an virtual operation index for a given time or blockindex

            :param blocktime: start time or start block index from which account
                operation should be fetched
            :type blocktime: int, datetime
            :param int stop_diff: Sets the difference between last estimation and
                new estimation at which the estimation stops. Must not be zero. (default is 1)
            :param int max_count: sets the maximum number of iterations. -1 disables this (default 100)

            .. testsetup::

                import pytz
                from beem.account import Account
                from beem.blockchain import Blockchain
                from datetime import datetime, timedelta
                from timeit import time as t

            .. testcode::

                utc = pytz.timezone('UTC')
                start_time = utc.localize(datetime.utcnow()) - timedelta(days=7)
                acc = Account("gtg")
                start_op = acc.estimate_virtual_op_num(start_time)

                b = Blockchain()
                start_block_num = b.get_estimated_block_num(start_time)
                start_op2 = acc.estimate_virtual_op_num(start_block_num)

            .. testcode::

                acc = Account("gtg")
                block_num = 21248120
                start = t.time()
                op_num = acc.estimate_virtual_op_num(block_num, stop_diff=1, max_count=10)
                stop = t.time()
                print(stop - start)
                for h in acc.get_account_history(op_num, 0):
                    block_est = h["block"]
                print(block_est - block_num)

        """
        max_index = self.virtual_op_count()
        if max_index < stop_diff:
            return 0

        # calculate everything with block numbers
        if min_index is None:
            created, min_index = self._get_first_blocknum()
        else:
            created = self._get_blocknum_from_hist(0, min_index=min_index)

        # convert blocktime to block number if given as a datetime/date/time
        if isinstance(blocktime, (datetime, date, time)):
            b = Blockchain(blockchain_instance=self.blockchain)
            target_blocknum = b.get_estimated_block_num(addTzInfo(blocktime), accurate=True)
        else:
            target_blocknum = blocktime

        # the requested blocknum/timestamp is before the account creation date
        if target_blocknum <= created:
            return 0

        # get the block number from the account's latest operation
        latest_blocknum = self._get_blocknum_from_hist(-1, min_index=min_index)

        # requested blocknum/timestamp is after the latest account operation
        if target_blocknum >= latest_blocknum:
            return max_index

        # all account ops in a single block
        if latest_blocknum - created == 0:
            return 0

        # set initial search range
        op_num = 0
        op_lower = 0
        block_lower = created
        op_upper = max_index
        block_upper = latest_blocknum
        last_op_num = None
        cnt = 0

        while True:
            # check if the maximum number of iterations was reached
            if max_count != -1 and cnt >= max_count:
                # did not converge, return the current state
                return op_num

            # linear approximation between the known upper and
            # lower bounds for the first iteration
            if cnt < 1:
                op_num = int((target_blocknum - block_lower) / (block_upper - block_lower) * (op_upper - op_lower) + op_lower)
            else:
                # divide and conquer for the following iterations
                op_num = int((op_upper + op_lower) / 2)
                if op_upper == op_lower + 1:  # round up if we're close to target
                    op_num += 1

            # get block number for current op number estimation
            if op_num != last_op_num:
                block_num = self._get_blocknum_from_hist(op_num, min_index=min_index)
                while block_num is None and op_num < max_index:
                    op_num += 1
                    block_num = self._get_blocknum_from_hist(op_num, min_index=min_index)
                last_op_num = op_num

            # check if the required accuracy was reached
            if op_upper - op_lower <= stop_diff or \
               op_upper == op_lower + 1:
                return op_num

            # set new upper/lower boundaries for next iteration
            if block_num < target_blocknum:
                # current op number was too low -> search upwards
                op_lower = op_num
                block_lower = block_num
            else:
                # current op number was too high or matched the target block
                # -> search downwards
                op_upper = op_num
                block_upper = block_num
            cnt += 1

    def get_curation_reward(self, days=7):
        """Returns the curation reward of the last `days` days

            :param int days: limit number of days to be included int the return value
        """
        stop = addTzInfo(datetime.utcnow()) - timedelta(days=days)
        reward_vests = Amount(0, self.blockchain.vest_token_symbol, blockchain_instance=self.blockchain)
        for reward in self.history_reverse(stop=stop, use_block_num=False, only_ops=["curation_reward"]):
            reward_vests += Amount(reward['reward'], blockchain_instance=self.blockchain)
        return self.blockchain.vests_to_token_power(float(reward_vests))

    def curation_stats(self):
        """Returns the curation reward of the last 24h and 7d and the average
            of the last 7 days

            :returns: Account curation
            :rtype: dictionary

            Sample output:

            .. code-block:: js

                {
                    '24hr': 0.0,
                    '7d': 0.0,
                    'avg': 0.0
                }

        """
        return {"24hr": self.get_curation_reward(days=1),
                "7d": self.get_curation_reward(days=7),
                "avg": self.get_curation_reward(days=7) / 7}

    def _get_operation_filter(self, only_ops=[], exclude_ops=[]):
        from beembase.operationids import operations
        operation_filter_low = 0
        operation_filter_high = 0
        if len(only_ops) == 0 and len(exclude_ops) == 0:
            return None, None
        if len(only_ops) > 0:
            for op in only_ops:
                op_id = operations[op]
                if op_id <= 64:
                    operation_filter_low += 2**op_id
                else:
                    operation_filter_high += 2 ** (op_id - 64 - 1)
        else:
            for op in operations:
                op_id = operations[op]
                if op_id <= 64:
                    operation_filter_low += 2**op_id
                else:
                    operation_filter_high += 2 ** (op_id - 64 - 1)
            for op in exclude_ops:
                op_id = operations[op]
                if op_id <= 64:
                    operation_filter_low -= 2**op_id
                else:
                    operation_filter_high -= 2 ** (op_id - 64 - 1)
        return operation_filter_low, operation_filter_high
        

    def get_account_history(self, index, limit, order=-1, start=None, stop=None, use_block_num=True, only_ops=[], exclude_ops=[], raw_output=False):
        """ Returns a generator for individual account transactions. This call can be used in a
            ``for`` loop.

            :param int index: first number of transactions to return
            :param int limit: limit number of transactions to return
            :param start: start number/date of transactions to
                return (*optional*)
            :type start: int, datetime
            :param stop: stop number/date of transactions to
                return (*optional*)
            :type stop: int, datetime
            :param bool use_block_num: if true, start and stop are block numbers, otherwise virtual OP count numbers.
            :param array only_ops: Limit generator by these
                operations (*optional*)
            :param array exclude_ops: Exclude thse operations from
                generator (*optional*)
            :param int batch_size: internal api call batch size (*optional*)
            :param int order: 1 for chronological, -1 for reverse order
            :param bool raw_output: if False, the output is a dict, which
                includes all values. Otherwise, the output is list.

            .. note::

                only_ops and exclude_ops takes an array of strings:
                The full list of operation ID's can be found in
                beembase.operationids.ops.
                Example: ['transfer', 'vote']

        """
        if order != -1 and order != 1:
            raise ValueError("order must be -1 or 1!")
        # self.blockchain.rpc.set_next_node_on_empty_reply(True)
        operation_filter_low = None
        operation_filter_high = None
        if self.blockchain.rpc.url == 'https://api.hive.blog':
            operation_filter_low, operation_filter_high = self._get_operation_filter(only_ops=only_ops, exclude_ops=exclude_ops)
        try:
            txs = self._get_account_history(start=index, limit=limit, operation_filter_low=operation_filter_low, operation_filter_high=operation_filter_high)
        except FilteredItemNotFound:
            txs = []
        if txs is None:
            return
        start = addTzInfo(start)
        stop = addTzInfo(stop)

        if order == -1:
            txs_list = reversed(txs)
        else:
            txs_list = txs
        for item in txs_list:
            item_index, event = item        
            if start and isinstance(start, (datetime, date, time)):
                timediff = start - formatTimeString(event["timestamp"])
                if timediff.total_seconds() * float(order) > 0:
                    continue
            elif start is not None and use_block_num and order == 1 and event['block'] < start:
                continue
            elif start is not None and use_block_num and order == -1 and event['block'] > start:
                continue
            elif start is not None and not use_block_num and order == 1 and item_index < start:
                continue
            elif start is not None and not use_block_num and order == -1 and item_index > start:
                continue
            if stop is not None and isinstance(stop, (datetime, date, time)):
                timediff = stop - formatTimeString(event["timestamp"])
                if timediff.total_seconds() * float(order) < 0:
                    return
            elif stop is not None and use_block_num and order == 1 and event['block'] > stop:
                return
            elif stop is not None and use_block_num and order == -1 and event['block'] < stop:
                return
            elif stop is not None and not use_block_num and order == 1 and item_index > stop:
                return
            elif stop is not None and not use_block_num and order == -1 and item_index < stop:
                return

            if isinstance(event['op'], list):
                op_type, op = event['op']
            else:
                op_type = event['op']['type']
                if len(op_type) > 10 and op_type[len(op_type) - 10:] == "_operation":
                    op_type = op_type[:-10]
                op = event['op']['value']
            block_props = remove_from_dict(event, keys=['op'], keep_keys=False)

            def construct_op(account_name):
                # verbatim output from steemd
                if raw_output:
                    return item

                # index can change during reindexing in
                # future hard-forks. Thus we cannot take it for granted.
                immutable = op.copy()
                immutable.update(block_props)
                immutable.update({
                    'account': account_name,
                    'type': op_type,
                })
                _id = Blockchain.hash_op(immutable)
                immutable.update({
                    '_id': _id,
                    'index': item_index,
                })
                return immutable

            if exclude_ops and op_type in exclude_ops:
                continue
            if not only_ops or op_type in only_ops:
                yield construct_op(self["name"])

    def history(
        self, start=None, stop=None, use_block_num=True,
        only_ops=[], exclude_ops=[], batch_size=1000, raw_output=False
    ):
        """ Returns a generator for individual account transactions. The
            earlist operation will be first. This call can be used in a
            ``for`` loop.

            :param start: start number/date of transactions to return (*optional*)
            :type start: int, datetime
            :param stop: stop number/date of transactions to return (*optional*)
            :type stop: int, datetime
            :param bool use_block_num: if true, start and stop are block numbers,
                otherwise virtual OP count numbers.
            :param array only_ops: Limit generator by these
                operations (*optional*)
            :param array exclude_ops: Exclude thse operations from
                generator (*optional*)
            :param int batch_size: internal api call batch size (*optional*)
            :param bool raw_output: if False, the output is a dict, which
                includes all values. Otherwise, the output is list.

            .. note::
                only_ops and exclude_ops takes an array of strings:
                The full list of operation ID's can be found in
                beembase.operationids.ops.
                Example: ['transfer', 'vote']

            .. testsetup::

                from beem.account import Account
                from datetime import datetime

            .. testcode::

                acc = Account("gtg")
                max_op_count = acc.virtual_op_count()
                # Returns the 100 latest operations
                acc_op = []
                for h in acc.history(start=max_op_count - 99, stop=max_op_count, use_block_num=False):
                    acc_op.append(h)
                len(acc_op)

            .. testoutput::

                100

            .. testcode::

                acc = Account("test")
                max_block = 21990141
                # Returns the account operation inside the last 100 block. This can be empty.
                acc_op = []
                for h in acc.history(start=max_block - 99, stop=max_block, use_block_num=True):
                    acc_op.append(h)
                len(acc_op)

            .. testoutput::

                0

            .. testcode::

                acc = Account("test")
                start_time = datetime(2018, 3, 1, 0, 0, 0)
                stop_time = datetime(2018, 3, 2, 0, 0, 0)
                # Returns the account operation from 1.4.2018 back to 1.3.2018
                acc_op = []
                for h in acc.history(start=start_time, stop=stop_time):
                    acc_op.append(h)
                len(acc_op)

            .. testoutput::

                0

        """
        _limit = batch_size
        max_index = self.virtual_op_count()
        if not max_index:
            return
        start = addTzInfo(start)
        stop = addTzInfo(stop)
        if start is not None and not use_block_num and not isinstance(start, (datetime, date, time)):
            start_index = start
        elif start is not None and max_index > batch_size:
            created, min_index = self._get_first_blocknum()
            op_est = self.estimate_virtual_op_num(start, stop_diff=1, min_index=min_index)
            if op_est < min_index:
                op_est = min_index
            est_diff = 0
            if isinstance(start, (datetime, date, time)):
                for h in self.get_account_history(op_est, 0):
                    block_date = formatTimeString(h["timestamp"])
                while(op_est > est_diff + batch_size and block_date > start):
                    est_diff += batch_size
                    if op_est - est_diff < 0:
                        est_diff = op_est
                    for h in self.get_account_history(op_est - est_diff, 0):
                        block_date = formatTimeString(h["timestamp"])
            elif not isinstance(start, (datetime, date, time)):
                for h in self.get_account_history(op_est, 0):
                    block_num = h["block"]
                while(op_est > est_diff + batch_size and block_num > start):
                    est_diff += batch_size
                    if op_est - est_diff < 0:
                        est_diff = op_est
                    for h in self.get_account_history(op_est - est_diff, 0):
                        block_num = h["block"]
            start_index = op_est - est_diff
        else:
            start_index = 0
        
        if stop is not None and not use_block_num and not isinstance(stop, (datetime, date, time)):
            if start_index + stop < _limit:
                _limit = stop

        first = start_index + _limit - 1
        if first > max_index:
            _limit = max_index - start_index
            first = start_index + _limit - 1
        elif first < _limit and self.blockchain.rpc.url == "https://api.hive.blog":
            first = _limit - 1
        elif first < _limit and self.blockchain.rpc.url != "https://api.hive.blog":
            first = _limit
        last_round = False
        
        if _limit < 0:
            return
        last_item_index = -1
        
        if self.blockchain.rpc.url == 'https://api.hive.blog' and (len(only_ops) > 0 or len(exclude_ops) > 0):
            operation_filter = True
        else:
            operation_filter = False
            
        while True:
            # RPC call
            if first < _limit - 1 and self.blockchain.rpc.url == "https://api.hive.blog":
                first = _limit - 1
            elif first < _limit and self.blockchain.rpc.url != "https://api.hive.blog":
                first = _limit
            batch_count = 0
            for item in self.get_account_history(first, _limit, start=None, stop=None, order=1, only_ops=only_ops, exclude_ops=exclude_ops, raw_output=raw_output):
                batch_count += 1
                if raw_output:
                    item_index, event = item
                    op_type, op = event['op']
                    timestamp = event["timestamp"]
                    block_num = event["block"]
                else:
                    item_index = item['index']
                    op_type = item['type']
                    timestamp = item["timestamp"]
                    block_num = item["block"]
                if start is not None and isinstance(start, (datetime, date, time)):
                    timediff = start - formatTimeString(timestamp)
                    if timediff.total_seconds() > 0:
                        continue
                elif start is not None and use_block_num and block_num < start:
                    continue
                elif start is not None and not use_block_num and item_index < start:
                    continue
                elif last_item_index >= item_index:
                    continue
                if stop is not None and isinstance(stop, (datetime, date, time)):
                    timediff = stop - formatTimeString(timestamp)
                    if timediff.total_seconds() < 0:
                        first = max_index + _limit
                        return
                elif stop is not None and use_block_num and block_num > stop:
                    return
                elif stop is not None and not use_block_num and item_index > stop:
                    return
                if operation_filter:
                    yield item
                else:
                    if exclude_ops and op_type in exclude_ops:
                        continue
                    if not only_ops or op_type in only_ops:
                        yield item                
                last_item_index = item_index
            if first < max_index and first + _limit >= max_index and not last_round:
                _limit = max_index - first
                first = max_index
                last_round = True
            else:
                if operation_filter and batch_count < _limit and first + 2000 < max_index and _limit == 1000:
                    first += 2000
                else:
                    first += _limit
                if stop is not None and not use_block_num and isinstance(stop, int) and first >= stop + _limit + 1:
                    break
                elif first > max_index or last_round:
                    break

    def history_reverse(
        self, start=None, stop=None, use_block_num=True,
        only_ops=[], exclude_ops=[], batch_size=1000, raw_output=False
    ):
        """ Returns a generator for individual account transactions. The
            latest operation will be first. This call can be used in a
            ``for`` loop.

            :param start: start number/date of transactions to
                return. If negative the virtual_op_count is added. (*optional*)
            :type start: int, datetime
            :param stop: stop number/date of transactions to
                return. If negative the virtual_op_count is added. (*optional*)
            :type stop: int, datetime
            :param bool use_block_num: if true, start and stop are block numbers,
                otherwise virtual OP count numbers.
            :param array only_ops: Limit generator by these
                operations (*optional*)
            :param array exclude_ops: Exclude thse operations from
                generator (*optional*)
            :param int batch_size: internal api call batch size (*optional*)
            :param bool raw_output: if False, the output is a dict, which
                includes all values. Otherwise, the output is list.

            .. note::
                only_ops and exclude_ops takes an array of strings:
                The full list of operation ID's can be found in
                beembase.operationids.ops.
                Example: ['transfer', 'vote']

            .. testsetup::

                from beem.account import Account
                from datetime import datetime

            .. testcode::

                acc = Account("gtg")
                max_op_count = acc.virtual_op_count()
                # Returns the 100 latest operations
                acc_op = []
                for h in acc.history_reverse(start=max_op_count, stop=max_op_count - 99, use_block_num=False):
                    acc_op.append(h)
                len(acc_op)

            .. testoutput::

                100

            .. testcode::

                max_block = 21990141
                acc = Account("test")
                # Returns the account operation inside the last 100 block. This can be empty.
                acc_op = []
                for h in acc.history_reverse(start=max_block, stop=max_block-100, use_block_num=True):
                    acc_op.append(h)
                len(acc_op)

            .. testoutput::

                0

            .. testcode::

                acc = Account("test")
                start_time = datetime(2018, 4, 1, 0, 0, 0)
                stop_time = datetime(2018, 3, 1, 0, 0, 0)
                # Returns the account operation from 1.4.2018 back to 1.3.2018
                acc_op = []
                for h in acc.history_reverse(start=start_time, stop=stop_time):
                    acc_op.append(h)
                len(acc_op)

            .. testoutput::

                0

        """
        _limit = batch_size
        first = self.virtual_op_count()
        start = addTzInfo(start)
        stop = addTzInfo(stop)
        if not first or not batch_size:
            return
        if start is not None and isinstance(start, int) and start < 0 and not use_block_num:
            start += first
        elif start is not None and isinstance(start, int) and not use_block_num:
            first = start
        elif start is not None and first > batch_size:
            created, min_index = self._get_first_blocknum()
            op_est = self.estimate_virtual_op_num(start, stop_diff=1, min_index=min_index)
            est_diff = 0
            if op_est < min_index:
                op_est = min_index
            if isinstance(start, (datetime, date, time)):
                for h in self.get_account_history(op_est, 0):
                    block_date = formatTimeString(h["timestamp"])
                while(op_est + est_diff + batch_size < first and block_date < start):
                    est_diff += batch_size
                    if op_est + est_diff > first:
                        est_diff = first - op_est
                    for h in self.get_account_history(op_est + est_diff, 0):
                        block_date = formatTimeString(h["timestamp"])
            else:
                for h in self.get_account_history(op_est, 0):
                    block_num = h["block"]
                while(op_est + est_diff + batch_size < first and block_num < start):
                    est_diff += batch_size
                    if op_est + est_diff > first:
                        est_diff = first - op_est
                    for h in self.get_account_history(op_est + est_diff, 0):
                        block_num = h["block"]
            first = op_est + est_diff
        if stop is not None and isinstance(stop, int) and stop < 0 and not use_block_num:
            stop += first
            
        if self.blockchain.rpc.url == 'https://api.hive.blog' and (len(only_ops) > 0 or len(exclude_ops) > 0):
            operation_filter = True
        else:
            operation_filter = False
        
        last_item_index = first + 1
        while True:
            # RPC call
            if first - _limit < 0 and self.blockchain.rpc.url == 'https://api.hive.blog':
                _limit = first + 1
            elif first - _limit < 0 and self.blockchain.rpc.url != 'https://api.hive.blog':
                _limit = first
            batch_count = 0
            for item in self.get_account_history(first, _limit, start=None, stop=None, order=-1, only_ops=only_ops, exclude_ops=exclude_ops, raw_output=raw_output):
                batch_count += 1
                if raw_output:
                    item_index, event = item
                    op_type, op = event['op']
                    timestamp = event["timestamp"]
                    block_num = event["block"]
                else:
                    item_index = item['index']
                    op_type = item['type']
                    timestamp = item["timestamp"]
                    block_num = item["block"]
                if start is not None and isinstance(start, (datetime, date, time)):
                    timediff = start - formatTimeString(timestamp)
                    if timediff.total_seconds() < 0:
                        continue
                elif start is not None and use_block_num and block_num > start:
                    continue
                elif start is not None and not use_block_num and item_index > start:
                    continue
                elif last_item_index <= item_index:
                    continue
                if stop is not None and isinstance(stop, (datetime, date, time)):
                    timediff = stop - formatTimeString(timestamp)
                    if timediff.total_seconds() > 0:
                        first = 0
                        return
                elif stop is not None and use_block_num and block_num < stop:
                    first = 0
                    return
                elif stop is not None and not use_block_num and item_index < stop:
                    first = 0
                    return
                if operation_filter:
                    yield item
                else:
                    if exclude_ops and op_type in exclude_ops:
                        continue
                    if not only_ops or op_type in only_ops:
                        yield item                    
                last_item_index = item_index
            if operation_filter and batch_count < _limit and _limit == 1000:
                first -= 2000
            else:
                first -= (_limit)
            if first < 1:
                break

    def mute(self, mute, account=None):
        """ Mute another account

            :param str mute: Mute this account
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)

        """
        return self.follow(mute, what=["ignore"], account=account)

    def unfollow(self, unfollow, account=None):
        """ Unfollow/Unmute another account's blog

            :param str unfollow: Unfollow/Unmute this account
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)

        """
        return self.follow(unfollow, what=[], account=account)

    def follow(self, other, what=["blog"], account=None):
        """ Follow/Unfollow/Mute/Unmute another account's blog

            .. note:: what can be one of the following on HIVE:
            blog, ignore, blacklist, unblacklist, follow_blacklist,
            unfollow_blacklist, follow_muted, unfollow_muted
            
            :param str/list other: Follow this account / accounts (only hive)
            :param list what: List of states to follow.
                ``['blog']`` means to follow ``other``,
                ``[]`` means to unfollow/unmute ``other``,
                ``['ignore']`` means to ignore ``other``,
                (defaults to ``['blog']``)
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)

        """
        if account is None:
            account = self["name"]
        account = extract_account_name(account)
        if not account:
            raise ValueError("You need to provide an account")
        if not other:
            raise ValueError("You need to provide an account to follow/unfollow/mute/unmute")
        if isinstance(other, str) and other.find(",") > 0:
            other = other.split(",")
        json_body = [
            'follow', {
                'follower': account,
                'following': other,
                'what': what
            }
        ]
        return self.blockchain.custom_json(
            "follow", json_body, required_posting_auths=[account])

    def update_account_profile(self, profile, account=None, **kwargs):
        """ Update an account's profile in json_metadata

            :param dict profile: The new profile to use
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)

            Sample profile structure:

            .. code-block:: js

                {
                    'name': 'Holger',
                    'about': 'beem Developer',
                    'location': 'Germany',
                    'profile_image': 'https://c1.staticflickr.com/5/4715/38733717165_7070227c89_n.jpg',
                    'cover_image': 'https://farm1.staticflickr.com/894/26382750057_69f5c8e568.jpg',
                    'website': 'https://github.com/holgern/beem'
                }

            .. code-block:: python

                from beem.account import Account
                account = Account("test")
                profile = account.profile
                profile["about"] = "test account"
                account.update_account_profile(profile)

        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)

        if not isinstance(profile, dict):
            raise ValueError("Profile must be a dict type!")

        if self['json_metadata'] == '':
            metadata = {}
        else:
            metadata = json.loads(self['json_metadata'])
        metadata["profile"] = profile
        return self.update_account_metadata(metadata)

    def update_account_metadata(self, metadata, account=None, **kwargs):
        """ Update an account's profile in json_metadata

            :param dict metadata: The new metadata to use
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)

        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        if isinstance(metadata, dict):
            metadata = json.dumps(metadata)
        elif not isinstance(metadata, str):
            raise ValueError("Profile must be a dict or string!")
        op = operations.Account_update(
            **{
                "account": account["name"],
                "memo_key": account["memo_key"],
                "json_metadata": metadata,
                "prefix": self.blockchain.prefix,
            })
        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def update_account_jsonmetadata(self, metadata, account=None, **kwargs):
        """ Update an account's profile in json_metadata using the posting key

            :param dict metadata: The new metadata to use
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)

        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        if isinstance(metadata, dict):
            metadata = json.dumps(metadata)
        elif not isinstance(metadata, str):
            raise ValueError("Profile must be a dict or string!")
        op = operations.Account_update2(
            **{
                "account": account["name"],
                "posting_json_metadata": metadata,
                "prefix": self.blockchain.prefix,
            })
        return self.blockchain.finalizeOp(op, account, "posting", **kwargs)

    # -------------------------------------------------------------------------
    #  Approval and Disapproval of witnesses
    # -------------------------------------------------------------------------
    def approvewitness(self, witness, account=None, approve=True, **kwargs):
        """ Approve a witness

            :param list witness: list of Witness name or id
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)

        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)

        # if not isinstance(witnesses, (list, set, tuple)):
        #     witnesses = {witnesses}

        # for witness in witnesses:
        #     witness = Witness(witness, blockchain_instance=self)

        op = operations.Account_witness_vote(**{
            "account": account["name"],
            "witness": witness,
            "approve": approve,
            "prefix": self.blockchain.prefix,
        })
        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def disapprovewitness(self, witness, account=None, **kwargs):
        """ Disapprove a witness

            :param list witness: list of Witness name or id
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)
        """
        return self.approvewitness(
            witness=witness, account=account, approve=False)

    def setproxy(self, proxy='', account=None):
        """ Set the witness and proposal system proxy of an account

        :param proxy: The account to set the proxy to (Leave empty for removing the proxy)
        :type proxy: str or Account
        :param account: The account the proxy should be set for
        :type account: str or Account
        """
        if account is None:
            account = self
        elif isinstance(account, Account):
            pass
        else:
            account = Account(account)

        if isinstance(proxy, str):
            proxy_name = proxy
        else:
            proxy_name = proxy["name"]
        op = operations.Account_witness_proxy(**{
            'account': account.name,
            'proxy': proxy_name
        })
        return self.blockchain.finalizeOp(op, account, 'active')

    def update_memo_key(self, key, account=None, **kwargs):
        """ Update an account's memo public key

            This method does **not** add any private keys to your
            wallet but merely changes the memo public key.

            :param str key: New memo public key
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)
        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)

        PublicKey(key, prefix=self.blockchain.prefix)

        account["memo_key"] = key
        op = operations.Account_update(**{
            "account": account["name"],
            "memo_key": account["memo_key"],
            "json_metadata": account["json_metadata"],
            "prefix": self.blockchain.prefix,
        })
        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def update_account_keys(self, new_password, account=None, **kwargs):
        """ Updates all account keys

            This method does **not** add any private keys to your
            wallet but merely changes the public keys.

            :param str new_password: is used to derive the owner, active,
                posting and memo key
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)
        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)

        key_auths = {}
        for role in ['owner', 'active', 'posting', 'memo']:
            pk = PasswordKey(account['name'], new_password, role=role)
            key_auths[role] = format(pk.get_public_key(), self.blockchain.prefix)

        op = operations.Account_update(**{
            "account": account["name"],
            'owner': {'account_auths': [],
                      'key_auths': [[key_auths['owner'], 1]],
                      "address_auths": [],
                      'weight_threshold': 1},
            'active': {'account_auths': [],
                       'key_auths': [[key_auths['active'], 1]],
                       "address_auths": [],
                       'weight_threshold': 1},
            'posting': {'account_auths': account['posting']['account_auths'],
                        'key_auths': [[key_auths['posting'], 1]],
                        "address_auths": [],
                        'weight_threshold': 1},
            'memo_key': key_auths['memo'],
            "json_metadata": account['json_metadata'],
            "prefix": self.blockchain.prefix,
        })

        return self.blockchain.finalizeOp(op, account, "owner", **kwargs)

    def change_recovery_account(self, new_recovery_account,
                                account=None, **kwargs):
        """Request a change of the recovery account.

        .. note:: It takes 30 days until the change applies. Another
            request within this time restarts the 30 day period.
            Setting the current recovery account again cancels any
            pending change request.

        :param str new_recovery_account: account name of the new
            recovery account
        :param str account: (optional) the account to change the
            recovery account for (defaults to ``default_account``)

        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        # Account() lookup to make sure the new account is valid
        new_rec_acc = Account(new_recovery_account,
                              blockchain_instance=self.blockchain)
        op = operations.Change_recovery_account(**{
            'account_to_recover': account['name'],
            'new_recovery_account': new_rec_acc['name'],
            'extensions': []
        })
        return self.blockchain.finalizeOp(op, account, "owner", **kwargs)

    # -------------------------------------------------------------------------
    # Simple Transfer
    # -------------------------------------------------------------------------
    def transfer(self, to, amount, asset, memo="", skip_account_check=False, account=None, **kwargs):
        """ Transfer an asset to another account.

            :param str to: Recipient
            :param float amount: Amount to transfer
            :param str asset: Asset to transfer
            :param str memo: (optional) Memo, may begin with `#` for encrypted
                messaging
            :param bool skip_account_check: (optional) When True, the receiver
                account name is not checked to speed up sending multiple transfers in a row
            :param str account: (optional) the source account for the transfer
                if not ``default_account``


            Transfer example:

            .. code-block:: python

                from beem.account import Account
                from beem import Hive
                active_wif = "5xxxx"
                stm = Hive(keys=[active_wif])
                acc = Account("test", blockchain_instance=stm)
                acc.transfer("test1", 1, "HIVE", "test")

        """

        if account is None:
            account = self  
        elif not skip_account_check:
            account = Account(account, blockchain_instance=self.blockchain)
        amount = Amount(amount, asset, blockchain_instance=self.blockchain)
        if not skip_account_check:
            to = Account(to, blockchain_instance=self.blockchain)

        to_name = extract_account_name(to)
        account_name = extract_account_name(account)        
        if memo and memo[0] == "#":
            from .memo import Memo
            memoObj = Memo(
                from_account=account,
                to_account=to,
                blockchain_instance=self.blockchain
            )
            memo = memoObj.encrypt(memo[1:])["message"]
        
        op = operations.Transfer(**{
            "amount": amount,
            "to": to_name,
            "memo": memo,
            "from": account_name,
            "prefix": self.blockchain.prefix,
            "json_str": not bool(self.blockchain.config["use_condenser"]),
        })
        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    #-------------------------------------------------------------------------------
    # Recurring Transfer added in hf25
    #-------------------------------------------------------------------------------
    def recurring_transfer(self, to, amount, asset, recurrence, executions, memo="", skip_account_check=False, account=None, **kwargs):
        """ Transfer an asset to another account.

            :param str to: Recipient
            :param float amount: Amount to transfer in each occurence, must have 3 decimal points
            :param str asset: Asset to transfer
            :param int recurrence: How often in hours to execute transfer
            :param int executions: Number of times to recur before stopping execution
            :param str memo: (optional) Memo, may begin with `#` for encrypted
                messaging
            :param bool skip_account_check: (optional) When True, the receiver
                account name is not checked to speed up sending multiple transfers in a row
            :param str account: (optional) the source account for the transfer
                if not ``default_account``


            Transfer example:

            .. code-block:: python

                from beem.account import Account
                from beem import Hive
                active_wif = "5xxxx"
                stm = Hive(keys=[active_wif])
                acc = Account("test", blockchain_instance=stm)
                acc.transfer("test1", 1, "HIVE", 48, 5, "test")

        """

        if account is None:
            account = self  
        elif not skip_account_check:
            account = Account(account, blockchain_instance=self.blockchain)
        amount = Amount(amount, asset, blockchain_instance=self.blockchain)
        if not skip_account_check:
            to = Account(to, blockchain_instance=self.blockchain)

        to_name = extract_account_name(to)
        account_name = extract_account_name(account)        
        if memo and memo[0] == "#":
            from .memo import Memo
            memoObj = Memo(
                from_account=account,
                to_account=to,
                blockchain_instance=self.blockchain
            )
            memo = memoObj.encrypt(memo[1:])["message"]
        
        op = operations.Recurring_transfer(**{
            "amount": amount,
            "to": to_name,
            "memo": memo,
            "from": account_name,
            "recurrence": recurrence,
            "executions": executions,
            "prefix": self.blockchain.prefix,
            "json_str": not bool(self.blockchain.config["use_condenser"]),
        })
        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def transfer_to_vesting(self, amount, to=None, account=None, skip_account_check=False, **kwargs):
        """ Vest STEEM

            :param float amount: Amount to transfer
            :param str to: Recipient (optional) if not set equal to account
            :param str account: (optional) the source account for the transfer
                if not ``default_account``
            :param bool skip_account_check: (optional) When True, the receiver
                account name is not checked to speed up sending multiple transfers in a row
        """
        if account is None:
            account = self
        elif not skip_account_check:
            account = Account(account, blockchain_instance=self.blockchain)
        amount = self._check_amount(amount, self.blockchain.token_symbol)
        if to is None and skip_account_check:
            to = self["name"]  # powerup on the same account
        elif to is None:
            to = self
        if not skip_account_check:
            to = Account(to, blockchain_instance=self.blockchain)
        to_name = extract_account_name(to)
        account_name = extract_account_name(account)           

        op = operations.Transfer_to_vesting(**{
            "from": account_name,
            "to": to_name,
            "amount": amount,
            "prefix": self.blockchain.prefix,
            "json_str": not bool(self.blockchain.config["use_condenser"]),
        })
        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def convert(self, amount, account=None, request_id=None):
        """ Convert SteemDollars to Steem (takes 3.5 days to settle)

            :param float amount: amount of SBD to convert
            :param str account: (optional) the source account for the transfer
                if not ``default_account``
            :param str request_id: (optional) identifier for tracking the
                conversion`

        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        amount = self._check_amount(amount, self.blockchain.backed_token_symbol)
        if request_id:
            request_id = int(request_id)
        else:
            request_id = random.getrandbits(32)  
        op = operations.Convert(
            **{
                "owner": account["name"],
                "requestid": request_id,
                "amount": amount,
                "prefix": self.blockchain.prefix,
                "json_str": not bool(self.blockchain.config["use_condenser"]),
            })

        return self.blockchain.finalizeOp(op, account, "active")

    #Added to differentiate and match the addition of the HF25 convert operation
    def collateralized_convert(self, amount, account=None, request_id=None, **kwargs):
        """ Convert Hive dollars to Hive (this method is meant to be more instant)
            and reflect the method added in HF25 

            :param float amount: amount of SBD to convert
            :param str account: (optional) the source account for the transfer
                if not ``default_account``
            :param str request_id: (optional) identifier for tracking the
                conversion`

        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        amount = self._check_amount(amount, self.blockchain.backed_token_symbol)
        if request_id:
            request_id = int(request_id)
        else:
            request_id = random.getrandbits(32)  
        op = operations.Collateralized_convert(
            **{
                "owner": account["name"],
                "requestid": request_id,
                "amount": amount,
                "prefix": self.blockchain.prefix,
                "json_str": not bool(self.blockchain.config["use_condenser"]),
            })

        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def transfer_to_savings(self, amount, asset, memo, to=None, account=None, **kwargs):
        """ Transfer SBD or STEEM into a 'savings' account.

            :param float amount: STEEM or SBD amount
            :param float asset: 'STEEM' or 'SBD'
            :param str memo: (optional) Memo
            :param str to: (optional) the source account for the transfer if
                not ``default_account``
            :param str account: (optional) the source account for the transfer
                if not ``default_account``

        """
        if asset not in [self.blockchain.token_symbol, self.blockchain.backed_token_symbol]:
            raise AssertionError()

        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)

        amount = Amount(amount, asset, blockchain_instance=self.blockchain)
        if to is None:
            to = account  # move to savings on same account
        else:
            to = Account(to, blockchain_instance=self.blockchain)
        op = operations.Transfer_to_savings(
            **{
                "from": account["name"],
                "to": to["name"],
                "amount": amount,
                "memo": memo,
                "prefix": self.blockchain.prefix,
                "json_str": not bool(self.blockchain.config["use_condenser"]),
            })
        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def transfer_from_savings(self,
                              amount,
                              asset,
                              memo,
                              request_id=None,
                              to=None,
                              account=None, **kwargs):
        """ Withdraw SBD or STEEM from 'savings' account.

            :param float amount: STEEM or SBD amount
            :param float asset: 'STEEM' or 'SBD'
            :param str memo: (optional) Memo
            :param str request_id: (optional) identifier for tracking or
                cancelling the withdrawal
            :param str to: (optional) the source account for the transfer if
                not ``default_account``
            :param str account: (optional) the source account for the transfer
                if not ``default_account``

        """
        if asset not in [self.blockchain.token_symbol, self.blockchain.backed_token_symbol]:
            raise AssertionError()

        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        if to is None:
            to = account  # move to savings on same account
        else:
            to = Account(to, blockchain_instance=self.blockchain)
        amount = Amount(amount, asset, blockchain_instance=self.blockchain)
        if request_id:
            request_id = int(request_id)
        else:
            request_id = random.getrandbits(32)

        op = operations.Transfer_from_savings(
            **{
                "from": account["name"],
                "request_id": request_id,
                "to": to["name"],
                "amount": amount,
                "memo": memo,
                "prefix": self.blockchain.prefix,
                "json_str": not bool(self.blockchain.config["use_condenser"]),
            })
        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def cancel_transfer_from_savings(self, request_id, account=None, **kwargs):
        """ Cancel a withdrawal from 'savings' account.

            :param str request_id: Identifier for tracking or cancelling
                the withdrawal
            :param str account: (optional) the source account for the transfer
                if not ``default_account``

        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        op = operations.Cancel_transfer_from_savings(**{
            "from": account["name"],
            "request_id": request_id,
            "prefix": self.blockchain.prefix,
        })
        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def _check_amount(self, amount, symbol):
        if isinstance(amount, (float, integer_types)):
            amount = Amount(amount, symbol, blockchain_instance=self.blockchain)
        elif isinstance(amount, string_types) and amount.replace('.', '', 1).replace(',', '', 1).isdigit():
            amount = Amount(float(amount), symbol, blockchain_instance=self.blockchain)
        else:
            amount = Amount(amount, blockchain_instance=self.blockchain)
        if not amount["symbol"] == symbol:
            raise AssertionError()
        return amount

    def claim_reward_balance(self,
                             reward_steem=0,
                             reward_sbd=0,
                             reward_hive=0,
                             reward_hbd=0,                             
                             reward_vests=0,
                             account=None, **kwargs):
        """ Claim reward balances.
        By default, this will claim ``all`` outstanding balances. To bypass
        this behaviour, set desired claim amount by setting any of
        `reward_steem`/``reward_hive, `reward_sbd`/``reward_hbd or `reward_vests`.

        :param str reward_steem: Amount of STEEM you would like to claim.
        :param str reward_hive: Amount of HIVE you would like to claim.
        :param str reward_sbd: Amount of SBD you would like to claim.
        :param str reward_hbd: Amount of HBD you would like to claim.
        :param str reward_vests: Amount of VESTS you would like to claim.
        :param str account: The source account for the claim if not
            ``default_account`` is used.

        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        if not account:
            raise ValueError("You need to provide an account")

        # if no values were set by user, claim all outstanding balances on
        # account

        reward_token_amount = self._check_amount(reward_steem + reward_hive, self.blockchain.token_symbol)
        reward_backed_token_amount = self._check_amount(reward_sbd + reward_hbd, self.blockchain.backed_token_symbol)
        reward_vests_amount = self._check_amount(reward_vests, self.blockchain.vest_token_symbol)

        if self.blockchain.is_hive:
            reward_token = "reward_hive"
            reward_backed_token = "reward_hbd"
        else:
            reward_token = "reward_steem"
            reward_backed_token = "reward_sbd"            

        if reward_token_amount.amount == 0 and reward_backed_token_amount.amount == 0 and reward_vests_amount.amount == 0:
            if len(account.balances["rewards"]) == 3:
                reward_token_amount = account.balances["rewards"][0]
                reward_backed_token_amount = account.balances["rewards"][1]
                reward_vests_amount = account.balances["rewards"][2]
            else:
                reward_token_amount = account.balances["rewards"][0]
                reward_vests_amount = account.balances["rewards"][1]
        if len(account.balances["rewards"]) == 3:
            op = operations.Claim_reward_balance(
                **{
                    "account": account["name"],
                    reward_token: reward_token_amount,
                    reward_backed_token: reward_backed_token_amount,
                    "reward_vests": reward_vests_amount,
                    "prefix": self.blockchain.prefix,
                })         
        else:
            op = operations.Claim_reward_balance(
                **{
                    "account": account["name"],
                    reward_token: reward_token_amount,
                    "reward_vests": reward_vests_amount,
                    "prefix": self.blockchain.prefix,
                })

        return self.blockchain.finalizeOp(op, account, "posting", **kwargs)

    def delegate_vesting_shares(self, to_account, vesting_shares,
                                account=None, **kwargs):
        """ Delegate SP to another account.

        :param str to_account: Account we are delegating shares to
            (delegatee).
        :param str vesting_shares: Amount of VESTS to delegate eg. `10000
            VESTS`.
        :param str account: The source account (delegator). If not specified,
            ``default_account`` is used.
        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        to_account = Account(to_account, blockchain_instance=self.blockchain)
        if to_account is None:
            raise ValueError("You need to provide a to_account")
        vesting_shares = self._check_amount(vesting_shares, self.blockchain.vest_token_symbol)

        op = operations.Delegate_vesting_shares(
            **{
                "delegator": account["name"],
                "delegatee": to_account["name"],
                "vesting_shares": vesting_shares,
                "prefix": self.blockchain.prefix,
            })
        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def withdraw_vesting(self, amount, account=None, **kwargs):
        """ Withdraw VESTS from the vesting account.

            :param float amount: number of VESTS to withdraw over a period of
                13 weeks
            :param str account: (optional) the source account for the transfer
                if not ``default_account``

    """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        amount = self._check_amount(amount, self.blockchain.vest_token_symbol)

        op = operations.Withdraw_vesting(
            **{
                "account": account["name"],
                "vesting_shares": amount,
                "prefix": self.blockchain.prefix,
            })

        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def set_withdraw_vesting_route(self,
                                   to,
                                   percentage=100,
                                   account=None,
                                   auto_vest=False, **kwargs):
        """ Set up a vesting withdraw route. When vesting shares are
            withdrawn, they will be routed to these accounts based on the
            specified weights.

            :param str to: Recipient of the vesting withdrawal
            :param float percentage: The percent of the withdraw to go
                to the 'to' account.
            :param str account: (optional) the vesting account
            :param bool auto_vest: Set to true if the 'to' account
                should receive the VESTS as VESTS, or false if it should
                receive them as STEEM. (defaults to ``False``)

        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        op = operations.Set_withdraw_vesting_route(
            **{
                "from_account": account["name"],
                "to_account": to,
                "percent": int(percentage * STEEM_1_PERCENT),
                "auto_vest": auto_vest
            })

        return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def allow(
        self, foreign, weight=None, permission="posting",
        account=None, threshold=None, **kwargs
    ):
        """ Give additional access to an account by some other public
            key or account.

            :param str foreign: The foreign account that will obtain access
            :param int weight: (optional) The weight to use. If not
                define, the threshold will be used. If the weight is
                smaller than the threshold, additional signatures will
                be required. (defaults to threshold)
            :param str permission: (optional) The actual permission to
                modify (defaults to ``posting``)
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)
            :param int threshold: (optional) The threshold that needs to be
                reached by signatures to be able to interact
        """
        from copy import deepcopy
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)

        if permission not in ["owner", "posting", "active"]:
            raise ValueError(
                "Permission needs to be either 'owner', 'posting', or 'active"
            )
        account = Account(account, blockchain_instance=self.blockchain)

        if permission not in account:
            account = Account(account, blockchain_instance=self.blockchain, lazy=False, full=True)
            account.clear_cache()
            account.refresh()
        if permission not in account:
            account = Account(account, blockchain_instance=self.blockchain)
        if permission not in account:
            raise AssertionError("Could not access permission")

        if not weight:
            weight = account[permission]["weight_threshold"]

        authority = deepcopy(account[permission])
        try:
            pubkey = PublicKey(foreign, prefix=self.blockchain.prefix)
            authority["key_auths"].append([
                str(pubkey),
                weight
            ])
        except:
            try:
                foreign_account = Account(foreign, blockchain_instance=self.blockchain)
                authority["account_auths"].append([
                    foreign_account["name"],
                    weight
                ])
            except:
                raise ValueError(
                    "Unknown foreign account or invalid public key"
                )
        if threshold:
            authority["weight_threshold"] = threshold
            self.blockchain._test_weights_treshold(authority)

        op = operations.Account_update(**{
            "account": account["name"],
            permission: authority,
            "memo_key": account["memo_key"],
            "json_metadata": account["json_metadata"],
            "prefix": self.blockchain.prefix
        })
        if permission == "owner":
            return self.blockchain.finalizeOp(op, account, "owner", **kwargs)
        else:
            return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def disallow(
        self, foreign, permission="posting",
        account=None, threshold=None, **kwargs
    ):
        """ Remove additional access to an account by some other public
            key or account.

            :param str foreign: The foreign account that will obtain access
            :param str permission: (optional) The actual permission to
                modify (defaults to ``posting``)
            :param str account: (optional) the account to allow access
                to (defaults to ``default_account``)
            :param int threshold: The threshold that needs to be reached
                by signatures to be able to interact
        """
        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)

        if permission not in ["owner", "active", "posting"]:
            raise ValueError(
                "Permission needs to be either 'owner', 'posting', or 'active"
            )
        authority = account[permission]

        try:
            pubkey = PublicKey(foreign, prefix=self.blockchain.prefix)
            affected_items = list(
                [x for x in authority["key_auths"] if x[0] == str(pubkey)])
            authority["key_auths"] = list([x for x in authority["key_auths"] if x[0] != str(pubkey)])
        except:
            try:
                foreign_account = Account(foreign, blockchain_instance=self.blockchain)
                affected_items = list(
                    [x for x in authority["account_auths"] if x[0] == foreign_account["name"]])
                authority["account_auths"] = list([x for x in authority["account_auths"] if x[0] != foreign_account["name"]])
            except:
                raise ValueError(
                    "Unknown foreign account or unvalid public key"
                )

        if not affected_items:
            raise ValueError("Changes nothing!")
        removed_weight = affected_items[0][1]

        # Define threshold
        if threshold:
            authority["weight_threshold"] = threshold

        # Correct threshold (at most by the amount removed from the
        # authority)
        try:
            self.blockchain._test_weights_treshold(authority)
        except:
            log.critical(
                "The account's threshold will be reduced by %d"
                % (removed_weight)
            )
            authority["weight_threshold"] -= removed_weight
            self.blockchain._test_weights_treshold(authority)

        op = operations.Account_update(**{
            "account": account["name"],
            permission: authority,
            "memo_key": account["memo_key"],
            "json_metadata": account["json_metadata"],
            "prefix": self.blockchain.prefix,
        })
        if permission == "owner":
            return self.blockchain.finalizeOp(op, account, "owner", **kwargs)
        else:
            return self.blockchain.finalizeOp(op, account, "active", **kwargs)

    def feed_history(self, limit=None, start_author=None, start_permlink=None,
                     account=None):
        """ Stream the feed entries of an account in reverse time order.

            .. note:: RPC nodes keep a limited history of entries for the
                      user feed. Older entries may not be available via this
                      call due to these node limitations.

            :param int limit: (optional) stream the latest `limit`
                feed entries. If unset (default), all available entries
                are streamed.
            :param str start_author: (optional) start streaming the
                replies from this author. `start_permlink=None`
                (default) starts with the latest available entry.
                If set, `start_permlink` has to be set as well.
            :param str start_permlink: (optional) start streaming the
                replies from this permlink. `start_permlink=None`
                (default) starts with the latest available entry.
                If set, `start_author` has to be set as well.
            :param str account: (optional) the account to get replies
                to (defaults to ``default_account``)

            comment_history_reverse example:

            .. code-block:: python

                from beem.account import Account
                from beem import Steem
                from beem.nodelist import NodeList
                nodelist = NodeList()
                nodelist.update_nodes()
                stm = Steem(node=nodelist.get_hive_nodes())
                acc = Account("ned", blockchain_instance=stm)
                for reply in acc.feed_history(limit=10):
                    print(reply)

        """
        if limit is not None:
            if not isinstance(limit, integer_types) or limit <= 0:
                raise AssertionError("`limit` has to be greater than 0`")
        if (start_author is None and start_permlink is not None) or \
           (start_author is not None and start_permlink is None):
            raise AssertionError("either both or none of `start_author` and "
                                 "`start_permlink` have to be set")

        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)
        feed_count = 0
        while True:
            query_limit = 100
            if limit is not None:
                query_limit = min(limit - feed_count + 1, query_limit)
            from .discussions import Query, Discussions_by_feed
            query = Query(start_author=start_author,
                          start_permlink=start_permlink, limit=query_limit,
                          tag=account['name'])
            results = Discussions_by_feed(query, blockchain_instance=self.blockchain)
            if len(results) == 0 or (start_permlink and len(results) == 1):
                return
            if feed_count > 0 and start_permlink:
                results = results[1:]  # strip duplicates from previous iteration
            for entry in results:
                feed_count += 1
                yield entry
                start_permlink = entry['permlink']
                start_author = entry['author']
                if feed_count == limit:
                    return

    def blog_history(self, limit=None, start=-1, reblogs=True, account=None):
        """ Stream the blog entries done by an account in reverse time order.

            .. note:: RPC nodes keep a limited history of entries for the
                      user blog. Older blog posts of an account may not be available
                      via this call due to these node limitations.

            :param int limit: (optional) stream the latest `limit`
                blog entries. If unset (default), all available blog
                entries are streamed.
            :param int start: (optional) start streaming the blog
                entries from this index. `start=-1` (default) starts
                with the latest available entry.
            :param bool reblogs: (optional) if set `True` (default)
                reblogs / resteems are included. If set `False`,
                reblogs/resteems are omitted.
            :param str account: (optional) the account to stream blog
                entries for (defaults to ``default_account``)

            blog_history_reverse example:

            .. code-block:: python

                from beem.account import Account
                from beem import Steem
                from beem.nodelist import NodeList
                nodelist = NodeList()
                nodelist.update_nodes()
                stm = Steem(node=nodelist.get_hive_nodes())
                acc = Account("steemitblog", blockchain_instance=stm)
                for post in acc.blog_history(limit=10):
                    print(post)

        """
        if limit is not None:
            if not isinstance(limit, integer_types) or limit <= 0:
                raise AssertionError("`limit` has to be greater than 0`")

        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)

        post_count = 0
        start_permlink = None
        start_author = None
        while True:
            query_limit = 100
            if limit is not None and reblogs:
                query_limit = min(limit - post_count + 1, query_limit)

            from .discussions import Discussions_by_blog
            query = {'start_author': start_author,
                     'start_permlink':start_permlink,
                     'limit': query_limit, 'tag': account['name']}
            results = Discussions_by_blog(query, blockchain_instance=self.blockchain)
            if len(results) == 0 or (start_permlink and len(results) == 1):
                return
            if start_permlink:
                results = results[1:]  # strip duplicates from previous iteration
            for post in results:
                if post['author'] == '':
                    continue
                if (reblogs or post['author'] == account['name']):
                    post_count += 1
                    yield post
                start_permlink = post['permlink']
                start_author = post['author']
                if post_count == limit:
                    return

    def comment_history(self, limit=None, start_permlink=None,
                        account=None):
        """ Stream the comments done by an account in reverse time order.

            .. note:: RPC nodes keep a limited history of user comments for the
                      user feed. Older comments may not be available via this
                      call due to these node limitations.

            :param int limit: (optional) stream the latest `limit`
                comments. If unset (default), all available comments
                are streamed.
            :param str start_permlink: (optional) start streaming the
                comments from this permlink. `start_permlink=None`
                (default) starts with the latest available entry.
            :param str account: (optional) the account to stream
                comments for (defaults to ``default_account``)

            comment_history_reverse example:

            .. code-block:: python

                from beem.account import Account
                from beem import Steem
                from beem.nodelist import NodeList
                nodelist = NodeList()
                nodelist.update_nodes()
                stm = Steem(node=nodelist.get_hive_nodes())
                acc = Account("ned", blockchain_instance=stm)
                for comment in acc.comment_history(limit=10):
                    print(comment)

        """
        if limit is not None:
            if not isinstance(limit, integer_types) or limit <= 0:
                raise AssertionError("`limit` has to be greater than 0`")

        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)

        comment_count = 0
        while True:
            query_limit = 100
            if limit is not None:
                query_limit = min(limit - comment_count + 1, query_limit)
            from .discussions import Discussions_by_comments
            query = {'start_author': account['name'],
                     'start_permlink': start_permlink, 'limit':
                     query_limit}
            results = Discussions_by_comments(query,
                                              blockchain_instance=self.blockchain)
            if len(results) == 0 or (start_permlink and len(results) == 1):
                return
            if comment_count > 0 and start_permlink:
                results = results[1:]  # strip duplicates from previous iteration
            for comment in results:
                if comment["permlink"] == '':
                    continue
                comment_count += 1
                yield comment
                start_permlink = comment['permlink']
                if comment_count == limit:
                    return

    def reply_history(self, limit=None, start_author=None,
                      start_permlink=None, account=None):
        """ Stream the replies to an account in reverse time order.

            .. note:: RPC nodes keep a limited history of entries for the
                      replies to an author. Older replies to an account may
                      not be available via this call due to
                      these node limitations.

            :param int limit: (optional) stream the latest `limit`
                replies. If unset (default), all available replies
                are streamed.
            :param str start_author: (optional) start streaming the
                replies from this author. `start_permlink=None`
                (default) starts with the latest available entry.
                If set, `start_permlink` has to be set as well.
            :param str start_permlink: (optional) start streaming the
                replies from this permlink. `start_permlink=None`
                (default) starts with the latest available entry.
                If set, `start_author` has to be set as well.
            :param str account: (optional) the account to get replies
                to (defaults to ``default_account``)

            comment_history_reverse example:

            .. code-block:: python

                from beem.account import Account
                acc = Account("ned")
                for reply in acc.reply_history(limit=10):
                    print(reply)

        """
        if limit is not None:
            if not isinstance(limit, integer_types) or limit <= 0:
                raise AssertionError("`limit` has to be greater than 0`")
        if (start_author is None and start_permlink is not None) or \
           (start_author is not None and start_permlink is None):
            raise AssertionError("either both or none of `start_author` and "
                                 "`start_permlink` have to be set")

        if account is None:
            account = self
        else:
            account = Account(account, blockchain_instance=self.blockchain)

        if start_author is None:
            start_author = account['name']

        reply_count = 0
        while True:
            query_limit = 100
            if limit is not None:
                query_limit = min(limit - reply_count + 1, query_limit)
            from .discussions import Replies_by_last_update

            query = {'start_author': start_author,
                     'start_permlink': start_permlink, 'limit':
                     query_limit}
            results = Replies_by_last_update(query,
                                             blockchain_instance=self.blockchain)
            if len(results) == 0 or (start_permlink and len(results) == 1):
                return
            if reply_count > 0 and start_permlink:
                results = results[1:]  # strip duplicates from previous iteration
            for reply in results:
                if reply['author'] == '':
                    continue
                reply_count += 1
                yield reply
                start_author = reply['author']
                start_permlink = reply['permlink']
                if reply_count == limit:
                    return


class AccountsObject(list):
    def printAsTable(self):
        t = PrettyTable(["Name"])
        t.align = "l"
        for acc in self:
            t.add_row([acc['name']])
        print(t)

    def print_summarize_table(self, tag_type="Follower", return_str=False, **kwargs):
        t = PrettyTable([
            "Key", "Value"
        ])
        t.align = "r"
        t.add_row([tag_type + " count", str(len(self))])
        own_mvest = []
        eff_sp = []
        rep = []
        last_vote_h = []
        last_post_d = []
        no_vote = 0
        no_post = 0
        for f in self:
            rep.append(f.rep)
            own_mvest.append(float(f.balances["available"][2]) / 1e6)
            eff_sp.append(f.get_token_power())
            last_vote = addTzInfo(datetime.utcnow()) - (f["last_vote_time"])
            if last_vote.days >= 365:
                no_vote += 1
            else:
                last_vote_h.append(last_vote.total_seconds() / 60 / 60)
            last_post = addTzInfo(datetime.utcnow()) - (f["last_root_post"])
            if last_post.days >= 365:
                no_post += 1
            else:
                last_post_d.append(last_post.total_seconds() / 60 / 60 / 24)

        t.add_row(["Summed MVest value", "%.2f" % sum(own_mvest)])
        if (len(rep) > 0):
            t.add_row(["Mean Rep.", "%.2f" % (sum(rep) / len(rep))])
            t.add_row(["Max Rep.", "%.2f" % (max(rep))])
        if (len(eff_sp) > 0):
            t.add_row(["Summed eff. SP", "%.2f" % sum(eff_sp)])
            t.add_row(["Mean eff. SP", "%.2f" % (sum(eff_sp) / len(eff_sp))])
            t.add_row(["Max eff. SP", "%.2f" % max(eff_sp)])
        if (len(last_vote_h) > 0):
            t.add_row(["Mean last vote diff in hours", "%.2f" % (sum(last_vote_h) / len(last_vote_h))])
        if len(last_post_d) > 0:
            t.add_row(["Mean last post diff in days", "%.2f" % (sum(last_post_d) / len(last_post_d))])
        t.add_row([tag_type + " without vote in 365 days", no_vote])
        t.add_row([tag_type + " without post in 365 days", no_post])
        if return_str:
            return t.get_string(**kwargs)
        else:
            print(t.get_string(**kwargs))


class Accounts(AccountsObject):
    """ Obtain a list of accounts

        :param list name_list: list of accounts to fetch
        :param int batch_limit: (optional) maximum number of accounts
            to fetch per call, defaults to 100
        :param Steem/Hive blockchain_instance: Steem() or Hive() instance to use when
            accessing a RPCcreator = Account(creator, blockchain_instance=self)
    """
    def __init__(self, name_list, batch_limit=100, lazy=False, full=True, blockchain_instance=None, **kwargs):
        
        if blockchain_instance is None:
            if kwargs.get("steem_instance"):
                blockchain_instance = kwargs["steem_instance"]
            elif kwargs.get("hive_instance"):
                blockchain_instance = kwargs["hive_instance"]
        self.blockchain = blockchain_instance or shared_blockchain_instance()

        if not self.blockchain.is_connected():
            return
        accounts = []
        name_cnt = 0

        while name_cnt < len(name_list):
            self.blockchain.rpc.set_next_node_on_empty_reply(False)
            if self.blockchain.rpc.get_use_appbase():
                accounts += self.blockchain.rpc.find_accounts({'accounts': name_list[name_cnt:batch_limit + name_cnt]}, api="database")["accounts"]
            else:
                accounts += self.blockchain.rpc.get_accounts(name_list[name_cnt:batch_limit + name_cnt])
            name_cnt += batch_limit

        super(Accounts, self).__init__(
            [
                Account(x, lazy=lazy, full=full, blockchain_instance=self.blockchain)
                for x in accounts
            ]
        )