Esempio n. 1
0
    def _submit_earn_batch_tx(
            self,
            sender: PrivateKey,
            earns: List[Earn],
            source: Optional[PrivateKey] = None,
            memo: Optional[str] = None) -> SubmitStellarTransactionResult:
        """ Submits a single transaction for a batch of earns. An error will be raised if the number of earns exceeds
        the capacity of a single transaction.

        :param sender: The :class:`PrivateKey <agora.model.keys.PrivateKey` of the sender
        :param earns: A list of :class:`Earn <agora.model.earn.Earn>` objects.
        :param source: (optional) The :class:`PrivateKey <agora.model.keys.PrivateKey` of the transaction source
            account. If not set, the sender will be used as the source.
        :param memo: (optional) The memo to include in the transaction. If set, none of the invoices included in earns
            will be applied.

        :return: a list of :class:`BatchEarnResult <agora.model.result.EarnResult>` objects
        """
        if len(earns) > 100:
            raise ValueError("cannot send more than 100 earns")

        builder = self._get_stellar_builder(source if source else sender)

        invoices = [earn.invoice for earn in earns if earn.invoice]
        invoice_list = InvoiceList(invoices) if invoices else None
        if memo:
            builder.add_text_memo(memo)
        elif self.app_index > 0:
            fk = invoice_list.get_sha_224_hash() if invoice_list else b''
            memo = AgoraMemo.new(1, TransactionType.EARN, self.app_index, fk)
            builder.add_hash_memo(memo.val)

        for earn in earns:
            builder.append_payment_op(
                earn.destination.stellar_address,
                quarks_to_kin(earn.quarks),
                source=sender.public_key.stellar_address,
            )

        if source:
            signers = [source, sender]
        else:
            signers = [sender]

        if self.whitelist_key:
            signers.append(self.whitelist_key)

        result = self._sign_and_submit_builder(signers, builder, invoice_list)
        if result.invoice_errors:
            # Invoice errors should not be triggered on earns. This indicates there is something wrong with the service.
            raise Error("unexpected invoice errors present")

        return result
Esempio n. 2
0
    def _assert_payment_envelope(
        envelope_xdr: bytes, signers: List[PrivateKey], tx_source: PrivateKey, base_fee: int, sequence: int,
        tx_memo: memo.Memo, payment: Payment
    ):
        envelope = te.TransactionEnvelope.from_xdr(base64.b64encode(envelope_xdr))
        operations = envelope.tx.operations

        TestAgoraClient._assert_envelope_properties(envelope, signers, tx_source, base_fee, sequence,
                                                    tx_memo)

        for idx, op in enumerate(operations):
            assert isinstance(op, operation.Payment)
            assert op.source == payment.sender.public_key.stellar_address
            assert op.destination == payment.destination.stellar_address
            assert op.amount == quarks_to_kin(payment.quarks)
Esempio n. 3
0
    def _submit_stellar_earn_batch_tx(
            self, batch: EarnBatch) -> SubmitTransactionResult:
        if len(batch.earns) > 100:
            raise ValueError('cannot send more than 100 earns')

        builder = self._get_stellar_builder(
            batch.channel if batch.channel else batch.sender)

        invoices = [earn.invoice for earn in batch.earns if earn.invoice]
        invoice_list = InvoiceList(invoices) if invoices else None
        if batch.memo:
            builder.add_text_memo(batch.memo)
        elif self.app_index > 0:
            fk = invoice_list.get_sha_224_hash() if invoice_list else b''
            memo = AgoraMemo.new(1, TransactionType.EARN, self.app_index, fk)
            builder.add_hash_memo(memo.val)

        for earn in batch.earns:
            # Inside the kin_base module, the base currency has been 'scaled' by a factor of 100 from
            # Stellar (i.e., the smallest denomination used is 1e-5 instead of 1e-7). However, Kin 2 uses the minimum
            # Stellar denomination of 1e-7.
            #
            # The Kin amounts provided to `append_payment_op` get converted to the smallest denomination inside the
            # submitted transaction and the conversion occurs assuming a smallest denomination of 1e-5. Therefore, for
            # Kin 2 transactions, we must multiple by 100 to account for the scaling factor.
            builder.append_payment_op(
                earn.destination.stellar_address,
                quarks_to_kin(earn.quarks *
                              100 if self._kin_version == 2 else earn.quarks),
                source=batch.sender.public_key.stellar_address,
                asset_issuer=self._asset_issuer
                if self._kin_version == 2 else None,
            )

        if batch.channel:
            signers = [batch.channel, batch.sender]
        else:
            signers = [batch.sender]

        if self.whitelist_key:
            signers.append(self.whitelist_key)

        result = self._sign_and_submit_builder(signers, builder, invoice_list)
        if result.invoice_errors:
            # Invoice errors should not be triggered on earns. This indicates there is something wrong with the service.
            raise Error('unexpected invoice errors present')

        return result
Esempio n. 4
0
    def _assert_earn_batch_envelope(
        envelope_xdr: bytes, signers: List[PrivateKey], tx_source: PrivateKey,
        base_fee: int, sequence: int, tx_memo: memo.Memo, sender: PrivateKey, earns: List[Earn]
    ):
        envelope = te.TransactionEnvelope.from_xdr(base64.b64encode(envelope_xdr))
        operations = envelope.tx.operations

        TestAgoraClient._assert_envelope_properties(envelope, signers, tx_source, base_fee * len(operations), sequence,
                                                    tx_memo)

        assert len(operations) == len(earns)
        for idx, op in enumerate(operations):
            earn = earns[idx]
            assert isinstance(op, operation.Payment)
            assert op.source == sender.public_key.stellar_address
            assert op.destination == earn.destination.stellar_address
            assert op.amount == quarks_to_kin(earn.quarks)
Esempio n. 5
0
    def _submit_stellar_payment_tx(
            self, payment: Payment) -> SubmitTransactionResult:
        builder = self._get_stellar_builder(
            payment.channel if payment.channel else payment.sender)

        invoice_list = None
        if payment.memo:
            builder.add_text_memo(payment.memo)
        elif self.app_index > 0:
            if payment.invoice:
                invoice_list = InvoiceList(invoices=[payment.invoice])

            fk = invoice_list.get_sha_224_hash() if payment.invoice else b''
            memo = AgoraMemo.new(1, payment.tx_type, self.app_index, fk)
            builder.add_hash_memo(memo.val)

        # Inside the kin_base module, the base currency has been 'scaled' by a factor of 100 from
        # Stellar (i.e., the smallest denomination used is 1e-5 instead of 1e-7). However, Kin 2 uses the minimum
        # Stellar denomination of 1e-7.
        #
        # The Kin amounts provided to `append_payment_op` get converted to the smallest denomination inside the
        # submitted transaction and the conversion occurs assuming a smallest denomination of 1e-5. Therefore, for
        # Kin 2 transactions, we must multiple by 100 to account for the scaling factor.
        builder.append_payment_op(
            payment.destination.stellar_address,
            quarks_to_kin(payment.quarks *
                          100 if self._kin_version == 2 else payment.quarks),
            source=payment.sender.public_key.stellar_address,
            asset_issuer=self._asset_issuer
            if self._kin_version == 2 else None,
        )

        if payment.channel:
            signers = [payment.channel, payment.sender]
        else:
            signers = [payment.sender]

        if self.whitelist_key:
            signers.append(self.whitelist_key)

        return self._sign_and_submit_builder(signers, builder, invoice_list)
Esempio n. 6
0
 def test_quarks_to_kin_str(self):
     assert quarks_to_kin(15) == "0.00015"
     assert quarks_to_kin(500000) == "5"
     assert quarks_to_kin(510000) == "5.1"
     assert quarks_to_kin(512345) == "5.12345"
Esempio n. 7
0
    def submit_payment(self, payment: Payment) -> bytes:
        if self._kin_version not in _SUPPORTED_VERSIONS:
            raise UnsupportedVersionError()

        if payment.invoice and self.app_index <= 0:
            raise ValueError(
                "cannot submit a payment with an invoice without an app index")

        builder = self._get_stellar_builder(
            payment.source if payment.source else payment.sender)

        invoice_list = None
        if payment.memo:
            builder.add_text_memo(payment.memo)
        elif self.app_index > 0:
            if payment.invoice:
                invoice_list = InvoiceList(invoices=[payment.invoice])

            fk = invoice_list.get_sha_224_hash() if payment.invoice else b''
            memo = AgoraMemo.new(1, payment.tx_type, self.app_index, fk)
            builder.add_hash_memo(memo.val)

        builder.append_payment_op(
            payment.destination.stellar_address,
            quarks_to_kin(payment.quarks),
            source=payment.sender.public_key.stellar_address,
        )

        if payment.source:
            signers = [payment.source, payment.sender]
        else:
            signers = [payment.sender]

        if self.whitelist_key:
            signers.append(self.whitelist_key)

        result = self._sign_and_submit_builder(signers, builder, invoice_list)
        if result.tx_error:
            if len(result.tx_error.op_errors) > 0:
                if len(result.tx_error.op_errors) != 1:
                    raise Error(
                        "invalid number of operation errors, expected 0 or 1, got {}"
                        .format(len(result.tx_error.op_errors)))
                raise result.tx_error.op_errors[0]

            if result.tx_error.tx_error:
                raise result.tx_error.tx_error

        if result.invoice_errors:
            if len(result.invoice_errors) != 1:
                raise Error(
                    "invalid number of invoice errors, expected 0 or 1, got {}"
                    .format(len(result.invoice_errors)))

            if result.invoice_errors[
                    0].reason == InvoiceErrorReason.ALREADY_PAID:
                raise AlreadyPaidError()
            if result.invoice_errors[
                    0].reason == InvoiceErrorReason.WRONG_DESTINATION:
                raise WrongDestinationError()
            if result.invoice_errors[
                    0].reason == InvoiceErrorReason.SKU_NOT_FOUND:
                raise SkuNotFoundError()
            raise Error("unknown invoice error: {}".format(
                result.invoice_errors[0].reason))

        return result.tx_hash