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, )
def payments_from_transaction( cls, tx: solana.Transaction, invoice_list: Optional[model_pb2.InvoiceList] = None ) -> List['ReadOnlyPayment']: """Returns a list of read only payments from a Solana transaction. :param tx: The transaction. :param invoice_list: (optional) A protobuf invoice list associated with the transaction. :return: A List of :class:`ReadOnlyPayment <ReadOnlyPayment>` objects. """ text_memo = None agora_memo = None start_index = 0 program_idx = tx.message.instructions[0].program_index if tx.message.accounts[program_idx] == solana.MEMO_PROGRAM_KEY: decompiled_memo = solana.decompile_memo(tx.message, 0) start_index = 1 memo_data = decompiled_memo.data.decode('utf-8') try: agora_memo = AgoraMemo.from_b64_string(memo_data) except ValueError: text_memo = memo_data transfer_count = (len(tx.message.instructions) - 1 if (text_memo or agora_memo) else len( tx.message.instructions)) if invoice_list and invoice_list.invoices and len( invoice_list.invoices) != transfer_count: raise ValueError( f'number of invoices ({len(invoice_list.invoices)}) does not match number of non-memo ' f'transaction instructions ({transfer_count})') payments = [] for idx, op in enumerate(tx.message.instructions[start_index:]): try: decompiled_transfer = solana.decompile_transfer( tx.message, idx + start_index) except ValueError as e: continue inv = invoice_list.invoices[ idx] if invoice_list and invoice_list.invoices else None payments.append( ReadOnlyPayment( sender=decompiled_transfer.source, destination=decompiled_transfer.dest, tx_type=agora_memo.tx_type() if agora_memo else TransactionType.UNKNOWN, quarks=decompiled_transfer.amount, invoice=Invoice.from_proto(inv) if inv else None, memo=text_memo if text_memo else None, )) return payments