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
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)
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
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)
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)
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"
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