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.º 2
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.º 4
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))