def health(self) -> Tuple[str, tuple, tuple]: """ Return health data for the passed symbol. Health data will include: Symbol, Status, Current Block, Node Version, Wallet Balance, and number of p2p connections (all as strings) :return tuple health_data: (manager_name:str, headings:list/tuple, health_data:list/tuple,) """ headers = ('Symbol', 'Status', 'API Node', 'Token Name', 'Issuer', 'Precision', 'Our Account', 'Our Balance') class_name = type(self).__name__ api_node = token_name = issuer = precision = our_account = balance = '' status = 'Okay' try: rpc = self.eng_rpc api_node = rpc.rpc.url our_account = self.coin.our_account if not rpc.account_exists(our_account): status = 'Account {} not found'.format(our_account) tk = rpc.get_token(self.symbol) if empty(tk, itr=True): raise exceptions.TokenNotFound('Token data was empty') tk = dict(tk) issuer = tk.get('issuer', 'ERROR GETTING ISSUER') token_name = tk.get('name', 'ERROR GETTING NAME') precision = str(tk.get('precision', 'ERROR GETTING PRECISION')) balance = self.balance(our_account) balance = ('{0:,.' + str(tk['precision']) + 'f}').format(balance) except exceptions.TokenNotFound: status = 'ERROR' token_name = '<b style="color: red">Token does not exist...</b>' except: status = 'ERROR' log.exception('Exception during %s.health for symbol %s', class_name, self.symbol) if status == 'Okay': status = '<b style="color: green">{}</b>'.format(status) else: status = '<b style="color: red">{}</b>'.format(status) data = (self.symbol, status, api_node, token_name, issuer, precision, our_account, balance) return class_name, headers, data
def send(self, amount, address, memo=None, from_address=None) -> dict: """ Send tokens to a given address/account, optionally specifying a memo. The Bitshares network transaction fee will be subtracted from the amount before sending. There must be a valid :py:class:`models.CryptoKeyPair` in the database for both 'active' and 'memo' keys for the from_address account, or an AuthorityMissing exception will be thrown. Example - send 1.23 BUILDTEAM from @someguy123 to @privex with memo 'hello' >>> s = BitsharesManager('BUILDTEAM') >>> s.send(from_address='someguy123', address='privex', amount=Decimal('1.23'), memo='hello') :param Decimal amount: Amount of tokens to send, as a Decimal() :param address: Account to send the tokens to :param from_address: Account to send the tokens from :param memo: Memo to send tokens with :raises AttributeError: When both `from_address` and `self.coin.our_account` are blank. :raises ArithmeticError: When the amount is lower than the lowest amount allowed by the token's precision (after subtracting the network transaction fee) :raises AuthorityMissing: Cannot send because we don't have authority to (missing key etc.) :raises AccountNotFound: The requested account/address doesn't exist :raises TokenNotFound: When the requested token `symbol` does not exist :raises NotEnoughBalance: The account `from_address` does not have enough balance to send this amount. :return dict: Result Information Format:: { txid:str - Transaction ID - None if not known, coin:str - Symbol that was sent, amount:Decimal - The amount that was sent (after fees), fee:Decimal - TX Fee that was taken from the amount, from:str - The account/address the coins were sent from, send_type:str - Should be statically set to "send" } """ # Try from_address first. If that's empty, try using self.coin.our_account. If both are empty, abort. if empty(from_address): if empty(self.coin.our_account): raise AttributeError( "Both 'from_address' and 'coin.our_account' are empty. Cannot send." ) from_address = self.coin.our_account # make sure we have the necessary private keys loaded (memo key for encrypting memo, active key for sending coins) self.set_wallet_keys(from_address, ['memo', 'active']) asset_obj = self.get_asset_obj(self.symbol) if asset_obj is None: raise exceptions.TokenNotFound( f'Failed to send because {self.symbol} is an invalid token symbol.' ) # trim input amount to the token's precision just to be safe str_amount = ('{0:.' + str(asset_obj['precision']) + 'f}').format(amount) amount = Decimal(str_amount) if not self.is_amount_above_minimum(amount, asset_obj['precision']): raise ArithmeticError( f'Failed to send because {amount} is less than the minimum amount allowed for {self.symbol} tokens.' ) from_account = self.get_account_obj(from_address) if from_account is None: raise exceptions.AccountNotFound( f'Failed to send because source account {from_address} could not be found.' ) to_account = self.get_account_obj(address) if to_account is None: raise exceptions.AccountNotFound( f'Failed to send because destination account {address} could not be found.' ) # verify from_account balance is sufficient for the transaction from_account_balance = self.get_decimal_from_amount( from_account.balance(self.symbol)) if from_account_balance < amount: raise exceptions.NotEnoughBalance( f'Failed to send because source account {from_address} balance {from_account_balance} {self.symbol} is less than amount to send ({amount} {self.symbol}).' ) amount_obj = Amount(str_amount, self.symbol, blockchain_instance=self.bitshares) try: if memo is None: memo = '' memo_obj = Memo(from_account=from_account, to_account=to_account, blockchain_instance=self.bitshares) encrypted_memo = memo_obj.encrypt(memo) # build preliminary transaction object, without network transaction fee op = operations.Transfer( **{ 'fee': { 'amount': 0, 'asset_id': amount_obj.asset['id'] }, 'from': from_account['id'], 'to': to_account['id'], 'amount': { 'amount': int(amount_obj), 'asset_id': amount_obj.asset['id'] }, 'memo': encrypted_memo, 'prefix': self.bitshares.prefix, }) # calculate how much the transaction fee is - rather clunky method here but it works ops = [self.bitshares.txbuffer.operation_class(op)] ops_with_fees = self.bitshares.txbuffer.add_required_fees( ops, asset_id=amount_obj.asset['id']) raw_fee_amount = Decimal( str(ops_with_fees[0][1].data['fee']['amount'])) fee_amount_str = '{0:f}'.format( raw_fee_amount / (10**amount_obj.asset['precision'])) fee_amount = Amount(fee_amount_str, self.symbol, blockchain_instance=self.bitshares) amount_obj = amount_obj - fee_amount # verify the amount still makes sense after subtracting the transaction fee if int(amount_obj) < 1: raise ArithmeticError( f'Failed to send because {amount} is less than the network transaction fee of {fee_amount_str} {self.symbol} tokens.' ) # correct the transaction object to take into account the transaction fee adj_op = operations.Transfer( **{ 'fee': { 'amount': int(fee_amount), 'asset_id': amount_obj.asset['id'] }, 'from': from_account['id'], 'to': to_account['id'], 'amount': { 'amount': int(amount_obj), 'asset_id': amount_obj.asset['id'] }, 'memo': encrypted_memo, 'prefix': self.bitshares.prefix, }) log.debug( 'doing Bitshares transaction - from_address[%s], address[%s], amount[%s %s], fee_amount[%s], amount_obj[%s], memo[%s]', from_address, address, str_amount, self.symbol, fee_amount, amount_obj, memo) # and finally, do the op! self.bitshares.finalizeOp(adj_op, from_address, "active", fee_asset=amount_obj.asset['id']) result = self.bitshares.broadcast() except KeyNotFound as e: raise exceptions.AuthorityMissing(str(e)) return { 'txid': None, # transaction ID is not readily available from the Bitshares API 'coin': self.orig_symbol, 'amount': self.get_decimal_from_amount(amount_obj), 'fee': self.get_decimal_from_amount(fee_amount), 'from': from_address, 'send_type': 'send' }
def send(self, amount, address, memo=None, from_address=None) -> dict: """ Send tokens to a given address/account, optionally specifying a memo if supported Example - send 1.23 SGTK from @someguy123 to @privex with memo 'hello' >>> s = SteemEngineManager('SGTK') >>> s.send(from_address='someguy123', address='privex', amount=Decimal('1.23'), memo='hello') :param Decimal amount: Amount of tokens to send, as a Decimal() :param address: Account to send the tokens to :param from_address: Account to send the tokens from :param memo: Memo to send tokens with (if supported) :raises AttributeError: When both `from_address` and `self.coin.our_account` are blank. :raises ArithmeticError: When the amount is lower than the lowest amount allowed by the token's precision :raises AuthorityMissing: Cannot send because we don't have authority to (missing key etc.) :raises AccountNotFound: The requested account/address doesn't exist :raises TokenNotFound: When the requested token `symbol` does not exist :raises NotEnoughBalance: The account `from_address` does not have enough balance to send this amount. :return dict: Result Information Format:: { txid:str - Transaction ID - None if not known, coin:str - Symbol that was sent, amount:Decimal - The amount that was sent (after fees), fee:Decimal - TX Fee that was taken from the amount, from:str - The account/address the coins were sent from, send_type:str - Should be statically set to "send" } """ # Try from_address first. If that's empty, try using self.coin.our_account. If both are empty, abort. if empty(from_address): if empty(self.coin.our_account): raise AttributeError("Both 'from_address' and 'coin.our_account' are empty. Cannot send.") from_address = self.coin.our_account try: token = self.eng_rpc.get_token(symbol=self.symbol) # If we get passed a float for some reason, make sure we trim it to the token's precision before # converting it to a Decimal. if type(amount) == float: amount = ('{0:.' + str(token['precision']) + 'f}').format(amount) amount = Decimal(amount) log.debug('Sending %f %s to @%s', amount, self.symbol, address) t = self.eng_rpc.send_token(symbol=self.symbol, from_acc=from_address, to_acc=address, amount=amount, memo=memo) txid = None # There's a risk we can't get the TXID, and so we fall back to None. if 'transaction_id' in t: txid = t['transaction_id'] return { 'txid': txid, 'coin': self.symbol, 'amount': amount, 'fee': Decimal(0), 'from': from_address, 'send_type': 'send' } except SENG.AccountNotFound as e: raise exceptions.AccountNotFound(str(e)) except SENG.TokenNotFound as e: raise exceptions.TokenNotFound(str(e)) except SENG.NotEnoughBalance as e: raise exceptions.NotEnoughBalance(str(e)) except MissingKeyError: raise exceptions.AuthorityMissing('Missing active key for sending account {}'.format(from_address))
def issue(self, amount: Decimal, address: str, memo: str = None, trigger_data=None) -> dict: """ Issue (create/print) tokens to a given address/account, optionally specifying a memo if desired. The network transaction fee for issuance will be paid by the issuing account in BTS. Example - Issue 5.10 SGTK to @privex >>> s = BitsharesManager('SGTK') >>> s.issue(address='privex', amount=Decimal('5.10')) :param Decimal amount: Amount of tokens to issue, as a Decimal :param address: Account to issue the tokens to (which is also the issuer account) :param memo: Optional memo for the issuance transaction :raises IssuerKeyError: Cannot issue because we don't have authority to (missing key etc.) :raises TokenNotFound: When the requested token `symbol` does not exist :raises AccountNotFound: The requested account doesn't exist :raises ArithmeticError: When the amount is lower than the lowest amount allowed by the token's precision :return dict: Result Information Format:: { txid:str - Transaction ID - None if not known, coin:str - Symbol that was sent, amount:Decimal - The amount that was issued, fee:Decimal - TX Fee that was taken from the amount (will be 0 if fee is in BTS rather than the issuing token), from:str - The account/address the coins were issued from, send_type:str - Should be statically set to "issue" } """ asset_obj = self.get_asset_obj(self.symbol) if asset_obj is None: raise exceptions.TokenNotFound(f'Failed to issue because {self.symbol} is an invalid token symbol.') # trim input amount to the token's precision just to be safe str_amount = ('{0:.' + str(asset_obj['precision']) + 'f}').format(amount) amount = Decimal(str_amount) if not self.is_amount_above_minimum(amount, asset_obj['precision']): raise ArithmeticError(f'Failed to issue because {amount} is less than the minimum amount allowed for {self.symbol} tokens.') to_account = self.get_account_obj(address) if to_account is None: raise exceptions.AccountNotFound(f'Failed to issue because issuing account {address} could not be found.') amount_obj = Amount(str_amount, self.symbol, blockchain_instance=self.bitshares) try: # make sure we have the necessary private keys loaded (memo key for encrypting memo, active key for issuing coins) self.set_wallet_keys(address, [ 'memo', 'active' ]) if memo is None: memo = '' memo_obj = Memo(from_account=to_account, to_account=to_account, blockchain_instance=self.bitshares) encrypted_memo = memo_obj.encrypt(memo) # construct the transaction - note that transaction fee for issuance will be paid in BTS, but we retain the # flexibility to add support for paying the fee in the issuing token if deemed necessary op = operations.Asset_issue( **{ "fee": {"amount": 0, "asset_id": "1.3.0"}, "issuer": to_account["id"], "asset_to_issue": {"amount": int(amount_obj), "asset_id": amount_obj.asset["id"]}, "memo": encrypted_memo, "issue_to_account": to_account["id"], "extensions": [], "prefix": self.bitshares.prefix, } ) log.debug('doing token issuance - address[%s], amount[%s %s], memo[%s]', address, str_amount, self.symbol, memo) # broadcast the transaction self.bitshares.finalizeOp(op, to_account, "active") result = self.bitshares.broadcast() except KeyNotFound as e: raise exceptions.IssuerKeyError(str(e)) except exceptions.AuthorityMissing as e: raise exceptions.IssuerKeyError(str(e)) return { 'txid': None, # transaction ID is not readily available from the Bitshares API 'coin': self.orig_symbol, 'amount': self.get_decimal_from_amount(amount_obj), 'fee': Decimal(0), # fee is in BTS, not the issuing token 'from': address, 'send_type': 'issue' }