def _generate_tx(with_il: Optional[bool] = False) -> Tuple[solana.Transaction, Optional[model_pb2.InvoiceList]]:
    il = None
    instructions = []

    if with_il:
        il = model_pb2.InvoiceList(
            invoices=[
                model_pb2.Invoice(
                    items=[
                        model_pb2.Invoice.LineItem(title='title1', description='desc1', amount=50, sku=b'somesku')
                    ]
                )
            ]
        )

        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)
        instructions.append(solana.memo_instruction(base64.b64encode(memo.val).decode('utf-8')))

    keys = [key.public_key for key in generate_keys(3)]
    instructions.append(solana.transfer(
        keys[0],
        keys[1],
        keys[2],
        20,
    ), )

    return solana.Transaction.new(
        _SIGNING_KEY.public_key,
        instructions
    ), il
예제 #2
0
    def from_json(cls, data: dict):
        kin_version = data.get('kin_version')
        if not kin_version:
            raise ValueError('kin_version is required')

        tx_hash = base64.b64decode(
            data.get('tx_hash') if 'tx_hash' in data else b'')
        if len(tx_hash) == 0:
            raise ValueError('tx_hash is required')

        il = data.get('invoice_list')
        if il:
            proto_il = model_pb2.InvoiceList()
            proto_il.ParseFromString(il)
            invoice_list = InvoiceList.from_proto(proto_il)
        else:
            invoice_list = None

        data = data.get('stellar_data')
        stellar_data = StellarData.from_json(data) if data else None

        return cls(kin_version,
                   tx_hash,
                   invoice_list=invoice_list,
                   stellar_data=stellar_data)
예제 #3
0
    def test_from_json_full(self):
        il = model_pb2.InvoiceList(
            invoices=[
                model_pb2.Invoice(
                    items=[
                        model_pb2.Invoice.LineItem(title='title1', description='desc1', amount=50, sku=b'somesku')
                    ]
                )
            ]
        )

        data = {
            'kin_version': 3,
            'tx_hash': base64.b64encode(b'txhash'),
            'invoice_list': il.SerializeToString(),
            'stellar_data': {
                'result_xdr': 'resultxdr',
                'envelope_xdr': 'envelopexdr',
            }
        }

        event = TransactionEvent.from_json(data)
        assert event.kin_version == 3
        assert event.tx_hash == b'txhash'
        assert len(event.invoice_list.invoices) == 1
        assert len(event.invoice_list.invoices[0].items) == 1

        line_item = event.invoice_list.invoices[0].items[0]
        assert line_item.title == 'title1'
        assert line_item.description == 'desc1'
        assert line_item.amount == 50
        assert line_item.sku == b'somesku'

        assert event.stellar_data.result_xdr == 'resultxdr'
        assert event.stellar_data.envelope_xdr == 'envelopexdr'
예제 #4
0
    def from_json(cls, data: dict) -> 'TransactionEvent':
        kin_version = data.get('kin_version')
        if not kin_version:
            raise ValueError('kin_version is required')
        if kin_version > 4 or kin_version < 2:
            raise ValueError(f'invalid kin version: {kin_version}')

        tx_id = base64.b64decode(data.get('tx_id')) if 'tx_id' in data else b''
        if len(tx_id) == 0:
            tx_id = base64.b64decode(
                data.get('tx_hash')) if 'tx_hash' in data else b''
            if len(tx_id) == 0:
                raise ValueError('`tx_id` or `tx_hash` is required')

        il = data.get('invoice_list')
        if il:
            proto_il = model_pb2.InvoiceList()
            proto_il.ParseFromString(il)
            invoice_list = InvoiceList.from_proto(proto_il)
        else:
            invoice_list = None

        tx_event = cls(kin_version, tx_id, invoice_list=invoice_list)

        solana_data = data.get('solana_event', None)
        if solana_data:
            tx_event.solana_event = SolanaEvent.from_json(solana_data)
        stellar_data = data.get('stellar_event', None)
        if stellar_data:
            tx_event.stellar_event = StellarEvent.from_json(stellar_data)

        return tx_event
예제 #5
0
    def test_get_transaction(self, grpc_channel, executor, app_index_client):
        tx_hash = b'somehash'
        future = executor.submit(app_index_client.get_transaction, tx_hash)

        _, request, rpc = grpc_channel.take_unary_unary(
            tx_pb.DESCRIPTOR.services_by_name['Transaction'].methods_by_name['GetTransaction']
        )

        # Create full response
        op_result = gen_payment_op_result(xdr_const.PAYMENT_SUCCESS)
        result_xdr = gen_result_xdr(xdr_const.txSUCCESS, [op_result, op_result])

        il = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(
                items=[
                    model_pb2.Invoice.LineItem(title='t1', amount=15),
                ]
            ),
        ])
        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.EARN, 1, fk)
        hash_memo = gen_hash_memo(memo.val)

        acc1 = gen_account_id()
        acc2 = gen_account_id()
        operations = [gen_payment_op(acc2, amount=15)]
        envelope_xdr = gen_tx_envelope_xdr(acc1, 1, operations, hash_memo)

        history_item = tx_pb.HistoryItem(
            hash=model_pb2.TransactionHash(value=tx_hash),
            result_xdr=result_xdr,
            envelope_xdr=envelope_xdr,
            cursor=tx_pb.Cursor(value=b'cursor1'),
            invoice_list=il,
        )
        resp = tx_pb.GetTransactionResponse(
            state=tx_pb.GetTransactionResponse.State.SUCCESS,
            ledger=10,
            item=history_item,
        )
        rpc.terminate(resp, (), grpc.StatusCode.OK, '')

        tx_data = future.result()
        assert tx_data.tx_hash == tx_hash
        assert len(tx_data.payments) == 1
        assert not tx_data.error

        payment1 = tx_data.payments[0]
        assert payment1.sender.raw == acc1.ed25519
        assert payment1.destination.raw == acc2.ed25519
        assert payment1.tx_type == memo.tx_type()
        assert payment1.quarks == 15
        assert (payment1.invoice.to_proto().SerializeToString() == il.invoices[0].SerializeToString())
        assert not payment1.memo

        assert request.transaction_hash.value == tx_hash
예제 #6
0
    def test_payments_from_transaction_with_invoice_list(self):
        il = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(
                items=[
                    model_pb2.Invoice.LineItem(title='t1', amount=10),
                ]
            ),
            model_pb2.Invoice(
                items=[
                    model_pb2.Invoice.LineItem(title='t1', amount=15),
                ]
            ),
        ])
        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)

        keys = [key.public_key for key in generate_keys(5)]
        token_program = keys[4]
        tx = solana.Transaction.new(
            keys[0],
            [
                solana.memo_instruction(base64.b64encode(memo.val).decode('utf-8')),
                solana.transfer(
                    keys[1],
                    keys[2],
                    keys[3],
                    20,
                    token_program,
                ),
                solana.transfer(
                    keys[2],
                    keys[3],
                    keys[1],
                    40,
                    token_program,
                ),
            ]
        )

        payments = ReadOnlyPayment.payments_from_transaction(tx, il)

        assert len(payments) == 2

        assert payments[0].sender == keys[1]
        assert payments[0].destination == keys[2]
        assert payments[0].tx_type == TransactionType.P2P
        assert payments[0].quarks == 20
        assert payments[0].invoice == Invoice.from_proto(il.invoices[0])
        assert not payments[0].memo

        assert payments[1].sender == keys[2]
        assert payments[1].destination == keys[3]
        assert payments[1].tx_type == TransactionType.P2P
        assert payments[1].quarks == 40
        assert payments[1].invoice == Invoice.from_proto(il.invoices[1])
        assert not payments[1].memo
예제 #7
0
 def _generate_invoice_list(transfer_count: int):
     return model_pb2.InvoiceList(
         invoices=[
             model_pb2.Invoice(
                 items=[
                     model_pb2.Invoice.LineItem(title=str(uuid.uuid4()))
                 ]
             ) for _ in range(transfer_count)
         ]
     )
예제 #8
0
    def test_from_proto_agora_memo(self):
        op_result = gen_payment_op_result(xdr_const.PAYMENT_SUCCESS)
        result_xdr = gen_result_xdr(xdr_const.txSUCCESS,
                                    [op_result, op_result])

        il = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(items=[
                model_pb2.Invoice.LineItem(title='t1', amount=10),
            ]),
            model_pb2.Invoice(items=[
                model_pb2.Invoice.LineItem(title='t1', amount=15),
            ]),
        ])
        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)
        hash_memo = gen_hash_memo(memo.val)

        acc1 = gen_account_id()
        acc2 = gen_account_id()
        acc3 = gen_account_id()
        operations = [
            gen_payment_op(acc2, src=acc1, amount=10),
            gen_payment_op(acc1, src=acc2, amount=15),
        ]
        envelope_xdr = gen_tx_envelope_xdr(acc3, 1, operations, hash_memo)

        history_item = tx_pb.HistoryItem(
            hash=model_pb2.TransactionHash(value=b'somehash'),
            result_xdr=result_xdr,
            envelope_xdr=envelope_xdr,
            cursor=tx_pb.Cursor(value=b'cursor1'),
            invoice_list=il,
        )

        data = TransactionData.from_proto(history_item)
        assert data.tx_hash == b'somehash'
        assert len(data.payments) == 2

        payment1 = data.payments[0]
        assert payment1.sender.raw == acc1.ed25519
        assert payment1.destination.raw == acc2.ed25519
        assert payment1.tx_type == memo.tx_type()
        assert payment1.quarks == 10
        assert (payment1.invoice.to_proto().SerializeToString() ==
                il.invoices[0].SerializeToString())
        assert not payment1.memo

        payment2 = data.payments[1]
        assert payment2.sender.raw == acc2.ed25519
        assert payment2.destination.raw == acc1.ed25519
        assert payment2.tx_type == TransactionType.P2P
        assert payment2.quarks == 15
        assert (payment2.invoice.to_proto().SerializeToString() ==
                il.invoices[1].SerializeToString())
        assert not payment2.memo
예제 #9
0
    def test_from_json_full_kin_4(self):
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, b'somefk')
        keys = [key.public_key for key in generate_keys(4)]
        token_program = keys[3]
        tx = solana.Transaction.new(keys[0], [
            solana.memo_instruction(
                base64.b64encode(memo.val).decode('utf-8')),
            solana.transfer(
                keys[1],
                keys[2],
                keys[3],
                20,
                token_program,
            ),
        ])

        il = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(items=[
                model_pb2.Invoice.LineItem(title='title1',
                                           description='desc1',
                                           amount=50,
                                           sku=b'somesku')
            ])
        ])

        data = {
            'kin_version': 4,
            'tx_id': base64.b64encode(b'txsig'),
            'invoice_list': il.SerializeToString(),
            'solana_event': {
                'transaction': base64.b64encode(tx.marshal()).decode('utf-8'),
                'transaction_error': 'bad_nonce',
                'transaction_error_raw': 'raw_error',
            }
        }

        event = TransactionEvent.from_json(data)
        assert event.kin_version == 4
        assert event.tx_id == b'txsig'
        assert len(event.invoice_list.invoices) == 1
        assert len(event.invoice_list.invoices[0].items) == 1

        line_item = event.invoice_list.invoices[0].items[0]
        assert line_item.title == 'title1'
        assert line_item.description == 'desc1'
        assert line_item.amount == 50
        assert line_item.sku == b'somesku'

        assert not event.stellar_event

        assert event.solana_event.transaction == tx
        assert isinstance(event.solana_event.tx_error, BadNonceError)
        assert event.solana_event.tx_error_raw == 'raw_error'
예제 #10
0
    def test_get_transaction(self, grpc_channel, executor, no_retry_client):
        source, dest = [key.public_key for key in generate_keys(2)]
        transaction_id = b'someid'
        future = executor.submit(no_retry_client.get_transaction,
                                 transaction_id)

        agora_memo = AgoraMemo.new(1, TransactionType.SPEND, 0, b'')
        tx = Transaction.new(PrivateKey.random().public_key, [
            memo.memo_instruction(
                base64.b64encode(agora_memo.val).decode('utf-8')),
            token.transfer(source, dest,
                           PrivateKey.random().public_key, 100),
        ])

        resp = tx_pb_v4.GetTransactionResponse(
            state=tx_pb_v4.GetTransactionResponse.State.SUCCESS,
            item=tx_pb_v4.HistoryItem(
                transaction_id=model_pb_v4.TransactionId(
                    value=transaction_id, ),
                solana_transaction=model_pb_v4.Transaction(
                    value=tx.marshal(), ),
                payments=[
                    tx_pb_v4.HistoryItem.Payment(
                        source=model_pb_v4.SolanaAccountId(value=source.raw),
                        destination=model_pb_v4.SolanaAccountId(
                            value=dest.raw),
                        amount=100,
                    )
                ],
                invoice_list=model_pb_v3.InvoiceList(invoices=[
                    model_pb_v3.Invoice(items=[
                        model_pb_v3.Invoice.LineItem(title='t1', amount=15),
                    ]),
                ])),
        )
        req = self._set_get_transaction_resp(grpc_channel, resp)
        assert req.transaction_id.value == transaction_id

        tx_data = future.result()
        assert tx_data.tx_id == transaction_id
        assert tx_data.transaction_state == TransactionState.SUCCESS
        assert len(tx_data.payments) == 1
        assert not tx_data.error

        p = tx_data.payments[0]
        assert p.sender.raw == source.raw
        assert p.destination.raw == dest.raw
        assert p.tx_type == TransactionType.SPEND
        assert p.quarks == 100
        assert p.invoice.to_proto().SerializeToString(
        ) == resp.item.invoice_list.invoices[0].SerializeToString()
        assert not p.memo
예제 #11
0
    def from_json(cls, data: dict):
        il_str = data.get('invoice_list')
        if il_str:
            proto_il = model_pb2.InvoiceList()
            proto_il.ParseFromString(base64.b64decode(il_str))
            il = InvoiceList.from_proto(proto_il)
        else:
            il = None

        tx_string = data.get('solana_transaction', "")
        if not tx_string:
            raise ValueError(
                '`solana_transaction` is required on Kin 4 transactions')

        tx = solana.Transaction.unmarshal(base64.b64decode(tx_string))
        creations, payments = parse_transaction(tx, il)
        return cls(creations, payments, tx)
예제 #12
0
    def test_from_proto(self):
        proto = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(items=[
                model_pb2.Invoice.LineItem(title='t1', amount=100),
            ]),
            model_pb2.Invoice(items=[
                model_pb2.Invoice.LineItem(title='t2', amount=150),
            ])
        ])

        invoice_list = InvoiceList.from_proto(proto)
        assert len(invoice_list.invoices) == len(proto.invoices)

        for idx, invoice in enumerate(invoice_list.invoices):
            proto_invoice = invoice_list.invoices[idx]
            assert len(invoice.items) == len(proto_invoice.items)
            assert invoice.items[0].title == proto_invoice.items[0].title
            assert invoice.items[0].amount == proto_invoice.items[0].amount
예제 #13
0
    def from_json(cls, data: dict) -> 'TransactionEvent':
        tx_id = base64.b64decode(data.get('tx_id')) if 'tx_id' in data else b''
        if len(tx_id) == 0:
            raise ValueError('`tx_id` is required')

        il = data.get('invoice_list')
        if il:
            proto_il = Parse(json.dumps(il), model_pb2.InvoiceList())
            invoice_list = InvoiceList.from_proto(proto_il)
        else:
            invoice_list = None

        solana_data = data.get('solana_event', None)
        if not solana_data:
            raise ValueError('`solana_event` is required')

        return cls(tx_id,
                   SolanaEvent.from_json(solana_data),
                   invoice_list=invoice_list)
예제 #14
0
    def test_payments_from_envelope_with_invoice_list(self):
        il = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(
                items=[
                    model_pb2.Invoice.LineItem(title='t1', amount=10),
                ]
            ),
            model_pb2.Invoice(
                items=[
                    model_pb2.Invoice.LineItem(title='t1', amount=15),
                ]
            ),
        ])
        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)
        hash_memo = gen_hash_memo(memo.val)

        acc1 = gen_account_id()
        acc2 = gen_account_id()
        acc3 = gen_account_id()
        operations = [gen_payment_op(acc2, amount=20),
                      gen_payment_op(acc3, src=acc2, amount=40)]
        envelope_xdr = gen_tx_envelope_xdr(acc1, 1, operations, hash_memo)
        env = te.TransactionEnvelope.from_xdr(base64.b64encode(envelope_xdr))

        payments = ReadOnlyPayment.payments_from_envelope(env, il)

        assert len(payments) == 2

        assert payments[0].sender.raw == acc1.ed25519
        assert payments[0].destination.raw == acc2.ed25519
        assert payments[0].tx_type == TransactionType.P2P
        assert payments[0].quarks == 20
        assert payments[0].invoice == Invoice.from_proto(il.invoices[0])
        assert not payments[0].memo

        assert payments[1].sender.raw == acc2.ed25519
        assert payments[1].destination.raw == acc3.ed25519
        assert payments[1].tx_type == TransactionType.P2P
        assert payments[1].quarks == 40
        assert payments[1].invoice == Invoice.from_proto(il.invoices[1])
        assert not payments[1].memo
예제 #15
0
    def test_from_json_kin_4(self):
        il = model_pb2.InvoiceList(
            invoices=[
                model_pb2.Invoice(
                    items=[
                        model_pb2.Invoice.LineItem(title='title1', description='desc1', amount=50, sku=b'somesku')
                    ]
                )
            ]
        )

        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)

        keys = [key.public_key for key in generate_keys(4)]
        token_program = keys[3]
        tx = solana.Transaction.new(
            keys[0],
            [
                solana.memo_instruction(base64.b64encode(memo.val).decode('utf-8')),
                solana.transfer(
                    keys[1],
                    keys[2],
                    keys[3],
                    20,
                    token_program,
                ),
            ]
        )

        data = {
            'kin_version': 4,
            'solana_transaction': base64.b64encode(tx.marshal()),
            'invoice_list': base64.b64encode(il.SerializeToString()),
        }

        req = SignTransactionRequest.from_json(data, Environment.TEST)
        assert len(req.payments) == 1
        assert req.payments[0].invoice == Invoice.from_proto(il.invoices[0])

        assert req.kin_version == data['kin_version']
        assert req.transaction == tx
예제 #16
0
    def from_json(cls, data: dict):
        envelope_xdr = data.get('envelope_xdr', "")
        if len(envelope_xdr) == 0:
            raise ValueError('envelope_xdr is required')
        env = te.TransactionEnvelope.from_xdr(envelope_xdr)

        kin_version = data.get('kin_version')
        if not kin_version:
            raise ValueError('kin_version is required')

        il_str = data.get('invoice_list')
        if il_str:
            proto_il = model_pb2.InvoiceList()
            proto_il.ParseFromString(base64.b64decode(il_str))
            il = InvoiceList.from_proto(proto_il)
        else:
            il = None

        return cls(ReadOnlyPayment.payments_from_envelope(env, il),
                   kin_version,
                   envelope=env)
예제 #17
0
    def from_json(cls, data: dict, environment: Environment):
        kin_version = data.get('kin_version')
        if not kin_version:
            kin_version = 3

        il_str = data.get('invoice_list')
        if il_str:
            proto_il = model_pb2.InvoiceList()
            proto_il.ParseFromString(base64.b64decode(il_str))
            il = InvoiceList.from_proto(proto_il)
        else:
            il = None

        if kin_version == 4:
            tx_string = data.get('solana_transaction', "")
            if not tx_string:
                raise ValueError(
                    '`solana_transaction` is required on Kin 4 transactions')

            tx = solana.Transaction.unmarshal(base64.b64decode(tx_string))
            return cls(ReadOnlyPayment.payments_from_transaction(tx, il),
                       kin_version,
                       transaction=tx)
        else:
            # Kin 2 or Kin 3 transaction
            envelope_xdr = data.get('envelope_xdr', "")
            if len(envelope_xdr) == 0:
                raise ValueError('envelope_xdr is required')

            if kin_version == 2:
                network_id = KIN_2_PROD_NETWORK if environment == Environment.PRODUCTION else KIN_2_TEST_NETWORK
                env = envelope_from_xdr(network_id, envelope_xdr)
            else:
                network_id = 'PUBLIC' if environment == Environment.PRODUCTION else 'TESTNET'
                env = envelope_from_xdr(network_id, envelope_xdr)

            return cls(ReadOnlyPayment.payments_from_envelope(
                env, il, kin_version=kin_version),
                       kin_version,
                       envelope=env)
예제 #18
0
    def test_from_json_full(self):
        envelope = _generate_envelope()
        il = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(items=[
                model_pb2.Invoice.LineItem(title='title1',
                                           description='desc1',
                                           amount=50,
                                           sku=b'somesku')
            ])
        ])

        data = {
            'kin_version': 3,
            'envelope_xdr': envelope.xdr(),
            'invoice_list': base64.b64encode(il.SerializeToString()),
        }

        req = SignTransactionRequest.from_json(data)
        assert len(req.payments) == 1
        assert req.payments[0].invoice == Invoice.from_proto(il.invoices[0])

        assert req.kin_version == data['kin_version']
        assert req.envelope.xdr() == envelope.xdr()
예제 #19
0
    def test_payments_from_transaction_invalid(self):
        il = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(
                items=[
                    model_pb2.Invoice.LineItem(title='t1', amount=10),
                ]
            ),
        ])
        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)

        keys = [key.public_key for key in generate_keys(5)]
        token_program = keys[4]
        tx = solana.Transaction.new(
            keys[0],
            [
                solana.memo_instruction(base64.b64encode(memo.val).decode('utf-8')),
                solana.transfer(
                    keys[1],
                    keys[2],
                    keys[3],
                    20,
                    token_program,
                ),
                solana.transfer(
                    keys[2],
                    keys[3],
                    keys[1],
                    40,
                    token_program,
                ),
            ]
        )

        # mismatching number of invoices and instructions
        with pytest.raises(ValueError):
            ReadOnlyPayment.payments_from_transaction(tx, il)
예제 #20
0
 def to_proto(self) -> model_pb2.InvoiceList:
     return model_pb2.InvoiceList(
         invoices=[invoice.to_proto() for invoice in self.invoices])
예제 #21
0
    def test_from_proto_solana_agora_memo(self):
        acc1, acc2, token_program = [
            key.public_key for key in generate_keys(3)
        ]
        il = model_pb_v3.InvoiceList(invoices=[
            model_pb_v3.Invoice(items=[
                model_pb_v3.Invoice.LineItem(title='t1', amount=10),
            ]),
            model_pb_v3.Invoice(items=[
                model_pb_v3.Invoice.LineItem(title='t1', amount=15),
            ]),
        ])
        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        agora_memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)

        tx = Transaction.new(PrivateKey.random().public_key, [
            memo_instruction(base64.b64encode(agora_memo.val).decode('utf-8')),
            transfer(acc1, acc2,
                     PrivateKey.random().public_key, 10),
            transfer(acc2, acc1,
                     PrivateKey.random().public_key, 15),
        ])

        history_item = tx_pb.HistoryItem(
            transaction_id=model_pb.TransactionId(value=b'somehash'),
            cursor=tx_pb.Cursor(value=b'cursor1'),
            solana_transaction=model_pb.Transaction(value=tx.marshal(), ),
            payments=[
                tx_pb.HistoryItem.Payment(
                    source=model_pb.SolanaAccountId(value=acc1.raw),
                    destination=model_pb.SolanaAccountId(value=acc2.raw),
                    amount=10,
                ),
                tx_pb.HistoryItem.Payment(
                    source=model_pb.SolanaAccountId(value=acc2.raw),
                    destination=model_pb.SolanaAccountId(value=acc1.raw),
                    amount=15,
                ),
            ],
            invoice_list=il,
        )

        data = TransactionData.from_proto(
            history_item, tx_pb.GetTransactionResponse.State.SUCCESS)
        assert data.tx_id == b'somehash'
        assert data.transaction_state == TransactionState.SUCCESS
        assert len(data.payments) == 2

        payment1 = data.payments[0]
        assert payment1.sender.raw == acc1.raw
        assert payment1.destination.raw == acc2.raw
        assert payment1.tx_type == TransactionType.P2P
        assert payment1.quarks == 10
        assert (payment1.invoice.to_proto().SerializeToString() ==
                il.invoices[0].SerializeToString())
        assert not payment1.memo

        payment2 = data.payments[1]
        assert payment2.sender.raw == acc2.raw
        assert payment2.destination.raw == acc1.raw
        assert payment2.tx_type == TransactionType.P2P
        assert payment2.quarks == 15
        assert (payment2.invoice.to_proto().SerializeToString() ==
                il.invoices[1].SerializeToString())
        assert not payment2.memo