Grouping pre-signed and unsigned transactions raising exceptions

I’ve created an lsig file following the buildweb3 tutorial.

goal clerk compile -a <PUBLIC KEY> -s step5.teal -o step5.lsig

I’ve adapted the step5.py script so that only the transaction with the lsig file is signed when sending the transactions. I had planned to have the other signed using Algosigner by passing the transaction ID.

However, I’m getting exceptions and am unsure how to solve them.

import json
import base64
from pathlib import Path

from algosdk.v2client import algod
from algosdk import transaction
from algosdk import encoding

from app import const


class AtomicSwap:

    def __init__(
        self,
        seller = const.SELLER_ADDR,
        receiver = const.RECEIVER,
        asset_id = const.ASSET_ID,
        asset_price = const.ASSET_PRICE,
        asset_count = 1,
        trxn_params=None
    ):
        self.seller = seller
        self.receiver = receiver
        self.asset_id = asset_id
        self.asset_count = asset_count
        self.asset_price = asset_price
        self.algod_client = connect_to_network()

    # utility for waiting on a transaction confirmation
    def wait_for_confirmation(self, txid):
        while True:
            txinfo = self.algod_client.pending_transaction_info(txid)
            if txinfo.get('round') and txinfo.get('round') > 0:
                print("Transaction {} confirmed in round {}.".format(txid, txinfo.get('round')))
                break
            else:
                print("Waiting for confirmation...")
                self.algod_client.status_after_block(algod_client.status().get('lastRound') +1)

    def build(self):

        # get suggested parameters
        params = self.algod_client.suggested_params()
        gh = params.gh
        first_valid_round = params.first
        last_valid_round = params.last
        fee = params.min_fee

        # create transaction 1 - Payment
        txn1 = transaction.PaymentTxn(
            self.receiver, fee, first_valid_round, last_valid_round, 
            gh, self.seller, int(self.asset_count*self.asset_price)
        )

        # create transaction 2 - Asset transfer
        txn2 = transaction.AssetTransferTxn(
            self.seller, fee, first_valid_round, 
            last_valid_round, gh, self.receiver, 
            self.asset_count, self.asset_id
        )

        # get group id and assign it to transactions
        gid = transaction.calculate_group_id([txn1, txn2])
        txn1.group = gid
        txn2.group = gid

        # sign transaction2
        with open(Path(const.smart_contracts_dir) / const.smart_contract_fname, "rb") as f:
            lsig = encoding.future_msgpack_decode(base64.b64encode(f.read()))
        stxn2 = transaction.LogicSigTransaction(txn2, lsig)

        signedGroup = [txn1, stxn2]
        
        # send them over network - FAILS HERE!
        txID = self.algod_client.send_transactions(
            signedGroup
        )
        
        wait_for_confirmation(self.algod_client, txID) 

        return txnID


def connect_to_network():
    """ utility to connect to node """
    algod_address = const.algod_address
    algod_token = const.algod_token
    algod_client = algod.AlgodClient(
            const.algod_token, 
            const.algod_address, 
            headers=const.purestake_token
    )
    return algod_client


sc = AtomicSwap()
print(sc.build())

But, when trying to send the transactions, I’m getting the error:

algosdk.error.AlgodHTTPError: {"message":"msgpack decode error [pos 5]: no matching struct field found when decoding stream map with key amt"}

I attempted to solve this by changing the headers passed to the purestake API from purestake_token = {'X-Api-key': algod_token} to purestake_token = {'X-Api-key': algod_token, 'content-type': 'application/x-binary'}.

But this fails with algosdk.error.AlgodHTTPError: {"message": "Unsupported Media Type"} when attempting to get suggested transactions.

How to solve this? And is my logic valid?

One potential issue is that you did not sign txn1.
You need to sign txn1 using the secret key of the sender of this transaction, aka self.receiver.
For that, you need the secret key of self.receiver.

Then signedGroup = [stxn1, stxn2] where stxn1 is the signed txn1.