Пример #1
0
    def from_proto(
            cls, item: tx_pb_v4.HistoryItem,
            state: tx_pb_v4.GetTransactionResponse.State) -> 'TransactionData':
        payments = []
        if item.invoice_list and item.invoice_list.invoices:
            if len(item.payments) != len(item.invoice_list.invoices):
                raise ValueError(
                    'number of invoices does not match number of payments')
            il = InvoiceList.from_proto(item.invoice_list)
        else:
            il = None

        tx_type = TransactionType.UNKNOWN
        memo = None
        if item.solana_transaction.value:
            solana_tx = solana.Transaction.unmarshal(
                item.solana_transaction.value)
            program_idx = solana_tx.message.instructions[0].program_index
            if solana_tx.message.accounts[
                    program_idx] == solana.MEMO_PROGRAM_KEY:
                decompiled_memo = solana.decompile_memo(solana_tx.message, 0)
                memo_data = decompiled_memo.data.decode('utf-8')
                try:
                    agora_memo = AgoraMemo.from_b64_string(memo_data)
                    tx_type = agora_memo.tx_type()
                except ValueError:
                    memo = memo_data
        elif item.stellar_transaction.envelope_xdr:
            env = te.TransactionEnvelope.from_xdr(
                base64.b64encode(item.stellar_transaction.envelope_xdr))
            tx = env.tx
            if isinstance(tx.memo, stellar_memo.HashMemo):
                try:
                    agora_memo = AgoraMemo.from_base_memo(tx.memo)
                    tx_type = agora_memo.tx_type()
                except ValueError:
                    pass
            elif isinstance(tx.memo, stellar_memo.TextMemo):
                memo = tx.memo.text.decode()

        for idx, p in enumerate(item.payments):
            inv = il.invoices[idx] if il and il.invoices else None
            payments.append(
                ReadOnlyPayment(PublicKey(p.source.value),
                                PublicKey(p.destination.value),
                                tx_type,
                                p.amount,
                                invoice=inv,
                                memo=memo))

        return cls(
            item.transaction_id.value,
            TransactionState.from_proto_v4(state),
            payments,
            error=TransactionErrors.from_proto_error(item.transaction_error)
            if item.transaction_error else None,
        )
Пример #2
0
    def test_cross_language(self):
        """Test parsing memos generated using the Go memo implementation.
        """
        # memo with an empty FK
        b64_encoded_memo = 'PVwrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
        hash_memo = memo.HashMemo(base64.b64decode(b64_encoded_memo))
        m = AgoraMemo.from_base_memo(hash_memo, False)
        assert m.version() == 7
        assert m.tx_type() == TransactionType.EARN
        assert m.app_index() == 51927
        assert m.foreign_key() == bytes(29)

        # memo with unknown tx type
        b64_encoded_memo = 'RQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
        hash_memo = memo.HashMemo(base64.b64decode(b64_encoded_memo))
        m = AgoraMemo.from_base_memo(hash_memo, False)
        assert m.version() == 1
        assert m.tx_type() == TransactionType.UNKNOWN
        assert m.tx_type_raw() == 10
        assert m.app_index() == 1
        assert m.foreign_key() == bytes(29)

        # memo with an invoice list hash
        b64_encoded_memo = 'ZQQAiLyJQCfEDmO0QOygz/PZOLDcbwP1FmbdtZ9E+wM='
        hash_memo = memo.HashMemo(base64.b64decode(b64_encoded_memo))

        expected_il = InvoiceList([
            Invoice([
                LineItem("Important Payment",
                         100000,
                         description="A very important payment",
                         sku=b'some sku')
            ])
        ])
        expected_fk = expected_il.get_sha_224_hash()

        m = AgoraMemo.from_base_memo(hash_memo, True)

        assert m.version() == 1
        assert m.tx_type() == TransactionType.P2P
        assert m.app_index() == 1

        # invoice hashes are only 28 bytes, so we ignore the 29th byte in the foreign key
        assert m.foreign_key()[:28] == expected_fk
Пример #3
0
    def test_from_xdr(self):
        valid_memo = AgoraMemo.new(2, TransactionType.EARN, 1, bytes(29))
        strictly_valid_memo = AgoraMemo.new(1, TransactionType.EARN, 1,
                                            bytes(29))

        with pytest.raises(ValueError):
            AgoraMemo.from_xdr(memo.TextMemo("text").to_xdr_object())

        actual = AgoraMemo.from_xdr(
            memo.HashMemo(valid_memo.val).to_xdr_object(), False)
        assert actual.val == valid_memo.val

        with pytest.raises(ValueError):
            AgoraMemo.from_base_memo(
                memo.HashMemo(valid_memo.val).to_xdr_object(), True)

        actual = AgoraMemo.from_xdr(
            memo.HashMemo(strictly_valid_memo.val).to_xdr_object(), True)
        assert actual.val == strictly_valid_memo.val
Пример #4
0
    def payments_from_envelope(
        cls,
        envelope: te.TransactionEnvelope,
        invoice_list: Optional[model_pb2.InvoiceList] = None
    ) -> List['ReadOnlyPayment']:
        """Returns a list of read only payments from a transaction envelope.

        :param envelope: A :class:`TransactionEnvelope <kin_base.transaction_envelope.TransactionEnvelope>.
        :param invoice_list: (optional) A protobuf invoice list associated with the transaction.
        :return: A List of :class:`ReadOnlyPayment <ReadOnlyPayment>` objects.
        """
        if invoice_list and invoice_list.invoices and len(
                invoice_list.invoices) != len(envelope.tx.operations):
            raise ValueError(
                "number of invoices ({}) does not match number of transaction operations ({})"
                .format(len(invoice_list.invoices),
                        len(envelope.tx.operations)))

        tx = envelope.tx

        text_memo = None
        agora_memo = None
        if isinstance(tx.memo, memo.HashMemo):
            try:
                agora_memo = AgoraMemo.from_base_memo(tx.memo, False)
            except ValueError:
                pass
        elif isinstance(tx.memo, memo.TextMemo):
            text_memo = tx.memo

        payments = []
        for idx, op in enumerate(envelope.tx.operations):
            # Currently, only payment operations are supported in this method. Eventually, create account and merge
            # account operations could potentially be supported, but currently this is primarily only used for payment
            # operations
            if not isinstance(op, operation.Payment):
                continue

            inv = invoice_list.invoices[
                idx] if invoice_list and invoice_list.invoices else None

            payments.append(
                ReadOnlyPayment(
                    sender=PublicKey.from_string(
                        op.source if op.source else tx.source.decode()),
                    destination=PublicKey.from_string(op.destination),
                    tx_type=agora_memo.tx_type()
                    if agora_memo else TransactionType.UNKNOWN,
                    quarks=kin_to_quarks(op.amount),
                    invoice=Invoice.from_proto(inv) if inv else None,
                    memo=text_memo.text.decode() if text_memo else None,
                ))

        return payments
Пример #5
0
    def payments_from_envelope(
        cls,
        envelope: te.TransactionEnvelope,
        invoice_list: Optional[model_pb2.InvoiceList] = None,
        kin_version: Optional[int] = 3,
    ) -> List['ReadOnlyPayment']:
        """Returns a list of read only payments from a transaction envelope.

        :param envelope: A :class:`TransactionEnvelope <kin_base.transaction_envelope.TransactionEnvelope>.
        :param invoice_list: (optional) A protobuf invoice list associated with the transaction.
        :param kin_version: (optional) The version of Kin to parse payments for.
        :return: A List of :class:`ReadOnlyPayment <ReadOnlyPayment>` objects.
        """
        if invoice_list and invoice_list.invoices and len(
                invoice_list.invoices) != len(envelope.tx.operations):
            raise ValueError(
                f'number of invoices ({len(invoice_list.invoices)}) does not match number of transaction '
                f'operations ({len(envelope.tx.operations)})')

        tx = envelope.tx

        text_memo = None
        agora_memo = None
        if isinstance(tx.memo, memo.HashMemo):
            try:
                agora_memo = AgoraMemo.from_base_memo(tx.memo, False)
            except ValueError:
                pass
        elif isinstance(tx.memo, memo.TextMemo):
            text_memo = tx.memo

        payments = []
        for idx, op in enumerate(envelope.tx.operations):
            # Currently, only payment operations are supported in this method. Eventually, create account and merge
            # account operations could potentially be supported, but currently this is primarily only used for payment
            # operations
            if not isinstance(op, operation.Payment):
                continue

            # Only Kin payment operations are supported in this method.
            if kin_version == 2 and (op.asset.type != 'credit_alphanum4'
                                     or op.asset.code != 'KIN'):
                continue

            inv = invoice_list.invoices[
                idx] if invoice_list and invoice_list.invoices else None

            # 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.
            #
            # When parsing an XDR transaction, `kin_base` assumes a smallest denomination of 1e-5. Therefore, for Kin 2
            # transactions, we must divide the resulting amounts by 100 to account for the 100x scaling factor.
            payments.append(
                ReadOnlyPayment(
                    sender=PublicKey.from_string(
                        op.source if op.source else tx.source.decode()),
                    destination=PublicKey.from_string(op.destination),
                    tx_type=agora_memo.tx_type()
                    if agora_memo else TransactionType.UNKNOWN,
                    quarks=int(kin_to_quarks(op.amount) / 100)
                    if kin_version == 2 else kin_to_quarks(op.amount),
                    invoice=Invoice.from_proto(inv) if inv else None,
                    memo=text_memo.text.decode() if text_memo else None,
                ))

        return payments