def appendTransferOpToTx(builder, from_name, to_name, amount, symbol):

    ## TODO: Cleanup exception catching for better user feedback

    try:
        account = Account(from_name, blockchain_instance=blockchain)
        amountAsset = Amount(amount, symbol, blockchain_instance=blockchain)
        to = Account(to_name, blockchain_instance=blockchain)
    except NumRetriesReached:
        Logger.Write("ERROR: Can't reach API node: 'NumRetries' reached.  Check network connection.")
        raise
    except:
        Logger.Write("Problem locating source or destination account, or asset. Might not exist.")
        raise

    memoObj = Memo(from_account=account, to_account=to, blockchain_instance=blockchain)
    memo_text = "" #"Signed by BitShares App on Ledger Nano S!"

    op = operations.Transfer(
        **{
            "fee": {"amount": 0, "asset_id": "1.3.0"},
            "from": account["id"],
            "to": to["id"],
            "amount": {"amount": int(amountAsset), "asset_id": amountAsset.asset["id"]},
            "memo": memoObj.encrypt(memo_text),
        }
    )

    builder.appendOps(op)
    return builder
Example #2
0
    def getMemo(self, from_account, to_account, text=None, data=None):
        if (data):
            from_account = data["from"]
            to_account = data["to"]
            #data = data["message"]

        #import traceback
        #try:
        #	from_account = self.getAccount(from_account)
        #except:
        #	traceback.print_exc()
        #	#print(from_account
        #	#print(from_account)
        try:
            to_account = self.getAccount(to_account)
            to_account_name = to_account["name"]
        except:
            to_account_name = to_account
            to_account = None
        #invert = True

        #to_account = self.getAccount(to_account)
        try:
            from_account = self.getAccount(from_account)
            from_account_name = from_account["name"]
        except:
            from_account_name = from_account
            from_account = None

        #print("TO:", to_account)
        #print("FROM:", from_account)
        #if (self.isCachedAccount(to_account['name'])):
        #	invert = False
        #else:
        #	if not(from_account):
        #		showerror(from_account_name)
        #		return False

        from bitshares.memo import Memo
        memoObj = Memo(
            from_account=None,  #from_account,
            to_account=None,  #to_account,
            bitshares_instance=self.bts)
        memoObj.chain_prefix = self.chain_prefix()
        memoObj.from_account = from_account
        memoObj.to_account = to_account
        if text:
            # { nonce, to from, message } json:
            return memoObj.encrypt(text)
        # plaintext:
        return {
            'message': memoObj.decrypt(data),
            'from': from_account_name,  #from_account['name']
            'to': to_account_name,  #to_account['name'],
        }
Example #3
0
def append_transfer_tx(append_to, dest_account_name):
    #
    #  `append_to` is a TransactionBuilder object. (E.g. from BitShares.new_tx())
    #  `dest_account_name` is a string account name.
    #
    account = Account(tip_sender, blockchain_instance=blockchain)
    amount = Amount(tip_amount,
                    tip_asset_symbol,
                    blockchain_instance=blockchain)
    try:
        to = Account(dest_account_name, blockchain_instance=blockchain)
    except NumRetriesReached:
        Logger.Write(
            "ERROR: Can't reach API node: 'NumRetries' reached.  Check network connection."
        )
        raise
    except:
        Logger.Write("Problem locating destination account. Might not exist.")
        raise
    memoObj = Memo(from_account=account,
                   to_account=to,
                   blockchain_instance=blockchain)
    memo_text = ""  #"Signed by BitShares App on Ledger Nano S!"

    op = operations.Transfer(
        **{
            "fee": {
                "amount": 0,
                "asset_id": "1.3.0"
            },
            "from": account["id"],
            "to": to["id"],
            "amount": {
                "amount": int(amount),
                "asset_id": amount.asset["id"]
            },
            "memo": memoObj.encrypt(memo_text),
        })

    append_to.appendOps(op)
    return append_to
    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 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'
        }