def balance(self,
                address: str = None,
                memo: str = None,
                memo_case: bool = False) -> Decimal:
        """
        Get token balance for a given Bitshares account, if memo is given - get total symbol amt received with this memo.

        :param address:    Bitshares account to get balance for, if not set, uses self.coin.our_account
        :param memo:       If not None, get total `self.symbol` received with this memo.
        :param memo_case:  Case sensitive memo search

        :raises AccountNotFound:  The requested account/address doesn't exist

        :return: Decimal(balance)
        """
        balance = Decimal(0)
        if address is None:
            address = self.coin.our_account

        if not empty(memo):
            raise NotImplemented(
                'Filtering by memos not implemented yet for BitsharesManager!')

        address = address.strip().lower()
        account_obj = self.get_account_obj(address)
        if account_obj is None:
            raise exceptions.AccountNotFound(
                f'Account {address} does not exist')

        asset_obj = self.get_asset_obj(self.symbol)
        if asset_obj is not None:
            amount_obj = account_obj.balance(self.symbol)
            balance = self.get_decimal_from_amount(amount_obj)

        return balance
Ejemplo n.º 2
0
    def issue(self, amount: Decimal, address: str, memo: str = None) -> dict:
        """
        Issue (create/print) tokens to a given address/account, optionally specifying a memo if supported

        Example - Issue 5.10 SGTK to @privex

            >>> s = SteemEngineManager('SGTK')
            >>> s.issue(address='privex', amount=Decimal('5.10'))

        :param Decimal amount:      Amount of tokens to issue, as a Decimal()
        :param address:             Address or account to issue the tokens to
        :param memo:                (ignored) Cannot issue tokens with a memo on SteemEngine
        :raises IssuerKeyError:     Cannot issue because we don't have authority to (missing key etc.)
        :raises IssueNotSupported:  Class does not support issuing, or requested symbol cannot be issued.
        :raises AccountNotFound:    The requested account/address doesn't exist
        :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 issued from,
              send_type:str       - Should be statically set to "issue"
          }

        """

        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)
            issuer = self.eng_rpc.get_token(self.symbol)['issuer']
            log.debug('Issuing %f %s to @%s', amount, self.symbol, address)
            t = self.eng_rpc.issue_token(symbol=self.symbol, to=address, amount=amount)
            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': issuer,
                'send_type': 'issue'
            }
        except SENG.AccountNotFound as e:
            raise exceptions.AccountNotFound(str(e))
        except MissingKeyError:
            raise exceptions.IssuerKeyError('Missing active key for issuer account {}'.format(issuer))
 def balance(self,
             address: str = None,
             memo: str = None,
             memo_case: bool = False) -> Decimal:
     """
     Returns fake balance for an address.
     If validate_addresses is disabled, will return a random number for addresses not in fake_balances
     """
     if address in self.fake_balances:
         return self.fake_balances[address]
     if self.random_balances and not self.validate_addresses:
         return fake_amt()
     raise exceptions.AccountNotFound(
         'Address {} does not exist in self.fake_balances'.format(address))
    def send(self, amount, address, from_address=None, memo=None) -> dict:
        amount = Decimal(amount)
        fee = fake_amt(0, amount / 2)
        # If validate_addresses is true, verify the address from the static list
        if not self.address_valid(address):
            raise exceptions.AccountNotFound(
                '(random/fake) Dest addr {} is not valid'.format(address))

        bal = self.balance(
            from_address) if from_address is not None else fake_amt()
        from_address = fake_user() if from_address is None else from_address
        if bal < amount:
            raise exceptions.NotEnoughBalance(
                'Tried sending {} from {} but addr only has {}'.format(
                    amount, from_address, bal))
        return {
            'txid': fake_txid(),
            'coin': self.symbol,
            'amount': amount - fee,
            'fee': fee,
            'from': from_address,
            'send_type': 'send'
        }
Ejemplo n.º 5
0
    def send(self,
             amount: Decimal,
             address: str,
             from_address: str = None,
             memo: str = None) -> dict:
        """
        Send a supported currency to a given address/account, optionally specifying a memo if supported

        Example - send 1.23 STEEM from @someguy123 to @privex with memo 'hello'

            >>> s = SteemManager('STEEM')
            >>> s.send(from_address='someguy123', address='privex', amount=Decimal('1.23'), memo='hello')

        :param Decimal amount:      Amount of currency to send, as a Decimal()
        :param address:             Account to send the currency to
        :param from_address:        Account to send the currency from
        :param memo:                Memo to send currency 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 asset's precision
        :raises AuthorityMissing:   Cannot send because we don't have authority to (missing key etc.)
        :raises AccountNotFound:    The requested account doesn't 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

        prec = self.precision
        sym = self.symbol
        memo = "" if empty(memo) else memo

        try:
            if type(amount) != Decimal:
                if type(amount) == float:
                    amount = ('{0:.' + str(self.precision) +
                              'f}').format(amount)
                amount = Decimal(amount)
            ###
            # Various sanity checks, e.g. checking amount is valid, to/from account are valid, we have
            # enough balance to send this amt, etc.
            ###
            if amount < Decimal(pow(10, -prec)):
                log.warning(
                    'Amount %s was passed, but is lower than precision for %s',
                    amount, sym)
                raise ArithmeticError(
                    'Amount {} is lower than token {}s precision of {} DP'.
                    format(amount, sym, prec))

            acc = Account(from_address, steem_instance=self.rpc)

            if not self.address_valid(address):
                raise exceptions.AccountNotFound(
                    'Destination account does not exist')
            if not self.address_valid(from_address):
                raise exceptions.AccountNotFound('From account does not exist')

            bal = self.balance(from_address)
            if bal < amount:
                raise exceptions.NotEnoughBalance(
                    'Account {} has balance {} but needs {} to send this tx'.
                    format(from_address, bal, amount))

            ###
            # Broadcast the transfer transaction on the network, and return the necessary data
            ###
            log.debug('Sending %f %s to @%s', amount, sym, address)
            tfr = acc.transfer(address, amount, sym, memo)
            # Beem's finalizeOp doesn't include TXID, so we try to find the TX on the blockchain after broadcast
            tx = self.find_steem_tx(tfr)

            log.debug('Success? TX Data - Transfer: %s Lookup TX: %s', tfr, tx)
            # Return TX data compatible with BaseManager standard
            return {
                # There's a risk we can't get the TXID, and so we fall back to None.
                'txid': tx.get('transaction_id', None),
                'coin': sym,
                'amount': amount,
                'fee': Decimal(0),
                'from': from_address,
                'send_type': 'send'
            }
        except MissingKeyError:
            raise exceptions.AuthorityMissing(
                'Missing active key for sending account {}'.format(
                    from_address))
    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'
        }
Ejemplo n.º 7
0
    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'
        }
Ejemplo n.º 9
0
    def send(self, amount, address, memo=None, from_address=None, trigger_data=None) -> dict:
        """
        Send the amount `amount` of `self.symbol` to a given address.

        Example - send 0.1 LTC to LVXXmgcVYBZAuiJM3V99uG48o3yG89h2Ph

            >>> s = BitcoinManager('LTC')
            >>> s.send(address='LVXXmgcVYBZAuiJM3V99uG48o3yG89h2Ph', amount=Decimal('0.1'))

        :param Decimal amount:      Amount of coins to send, as a Decimal()
        :param address:             Address to send the coins to
        :param from_address:        NOT USED BY THIS MANAGER
        :param memo:                NOT USED BY THIS MANAGER
        :raises AccountNotFound:    The destination `address` isn't valid
        :raises NotEnoughBalance:   The wallet 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"
          }

        """

        # To avoid issues with floats, we convert the amount to a string with 8DP
        if type(amount) == float:
            amount = '{0:.8f}'.format(amount)
        amount = Decimal(amount)

        # First let's make sure the destination address is valid
        try:
            v = self.rpc.validateaddress(address)
            if v['isvalid'] not in [True, 'true', 1]:
                raise Exception()
        except:
            raise exceptions.AccountNotFound('Invalid {} address {}'.format(self.symbol, address))
        # Now let's try to send the coins
        try:
            txid = self.rpc.sendtoaddress(address, '{0:.8f}'.format(amount), "", "", True,
                                          force_float=not self.setting['string_amt'])
            # Fallback values if getting TX data below fails.
            fee = Decimal(0)
            actual_amount = '{0:.8f}'.format(amount)
            sender = None
            # To find out the fee, the amount after fee, and the sending addresses, we need to look up the TXID
            # This is wrapped in another try/catch to ensure badly formed TXs don't trigger the outer try/catch
            # and cause the caller to think the coins weren't sent.
            try:
                txdata = self.rpc.gettransaction(txid)
                fee = txdata['fee']
                if type(fee) == float:
                    fee = '{0:.8f}'.format(fee)
                fee = Decimal(fee)
                if fee < 0:
                    fee = -fee
                txam = txdata['amount']
                if type(txam) == float:
                    txam = '{0:.8f}'.format(txam)
                txam = Decimal(txam)
                actual_amount = txam if txam > 0 else -txam
                sender = ','.join([a['address'] for a in txdata['details'] if a['category'] == 'send'])

            except:
                log.exception('Something went wrong loading data for %s TXID %s', self.symbol, txid)
                log.error('The fee, amount, and "from" details may be inaccurate')

            return {
                'txid': txid,
                'coin': self.symbol,
                'amount': Decimal(actual_amount),
                'fee': Decimal(fee),
                'from': sender,
                'send_type': 'send'
            }
        except Exception as e:
            log.exception("Something went wrong sending %f %s to %s", amount, self.symbol, address)
            raise e