Signing transactions in tx group result into error: 'The transaction group is incomplete or presented in a different order than when it was created.'

Hey folks,

I’m building 3 transactions to be added to a txgroup, the first two transactions needs to be signed by the user and the 3rd by a logic signature account. My code looks like that (I’m using algosigner for allowing users to sign txs on the client side). I’m getting an error:

  1. {message: ‘The transaction group is incomplete or presented in a different order than when it was created.’, code: 4300, name: ‘IncompleteOrDisorderedGroup’}

  2. code: 4300

  3. message: “The transaction group is incomplete or presented in a different order than when it was created.”

  4. name: “IncompleteOrDisorderedGroup”

when trying to sign the first two txs (buyCallTx, and payTx) using algosigner, any ideas why?

        // APPLICATION CALL TX
        let appArgs = [];
        const methodName = "buy";
        appArgs.push(utils.stringToUint8Array(methodName));
        let foreightAssets = [];

        let buyCallTx = await transactionWrapper.callApp(
            client,
            props.connectedAddress,
            undefined,
            +loadedSound['appID'],
            algosdk.OnApplicationComplete.NoOpOC,
            appArgs,
            foreightAssets,
            false);

        // PAYMENT TX BUYER -> SELLER
        let payTx = await transactionWrapper.pay(
            client,
            props.connectedAddress,
            loadedSound['owner'],
            +loadedSound['price'],
            undefined,
            false);

        // ASSET XFER: ESCROW -> BUYER
        console.log('3 asset xfer tx');
        let axferTx = await transactionWrapper.assetXfer(
            client,
            nftEscrowContract.escrowAddress,
            undefined,
            props.connectedAddress,
            loadedSound['owner'],
            1,
            undefined,
            +loadedSound['tokenID'],
            false);

        // ATOMIC TRANSFER of all txs in one
        let txns = [
            buyCallTx,
            payTx,
            axferTx
        ];

        console.log('building tx group');
        let txgroup = algosdk.assignGroupID(txns);

        console.log('tx group built');

        let signedBuyTx = await algosignerHelper.signTx(
            AlgoSigner, 
            client,
            buyCallTx);

        let signedPayTx = await algosignerHelper.signTx(
            AlgoSigner,
            client,
            payTx);

        // sign logic sig
        let signedAxferTx = algosdk.signLogicSigTransactionObject(axferTx, nftEscrowContract.lsigAccount).blob;

        let signedTxs = [
            signedBuyTx,
            signedPayTx,
            signedAxferTx];
    
        console.log('attempting to send all signed txs...');

        let tx = undefined;
        try
        {
            tx = (await client.sendRawTransaction(signedTxs).do());
            console.log("Transaction : " + tx.txId);
        }
        catch(err)
        {
            console.log(err);
            throw err
        }
    
        // Wait for transaction to be confirmed
        const confirmedTxn = await algosdk.waitForConfirmation(client, tx.txId, 4);
        //Get the completed Transaction
        console.log("Transaction " + tx.txId + " confirmed in round " + confirmedTxn["confirmed-round"]);

My algosigner helper function looks like this:

const signTx = async (
    AlgoSigner,
    client,
    unsignedTx) => {

    try
    {
        let unsigned_tx_b64 = AlgoSigner.encoding.msgpackToBase64(unsignedTx.toByte());
        const signedTxns = await AlgoSigner.signTxn([{txn: unsigned_tx_b64}]);

        console.log('signed txns:' + signedTxns);
        return signedTxns[0];
    }
    catch(error)
    {
        console.error(error);
        return null;
    }
}

Anyone has any ideas why I’m getting this error?

Thanks

I’ve asked an AlgoSigner dev to stop by and assist

Thanks Tim, I’m not sure if it’s an AlgoSigner issue, it seems like using let txgroup = algosdk.assignGroupID(txns); prior to signing with AlgoSigner gives this error? It will be good to get their feedback too though for sure.

Thanks

Hello Kubot,

As mentioned by the error you need to provide all of the transactions in a group for AlgoSigner to sign them, this is to prevent dApps from trying to trick users into signing only some of the atomic txs without the user knowing what the rest of the atomic txs in that group do.

You can do this by doing a single call to AlgoSigner like so:
AlgoSigner.signTxn([ {txn: buyCallBase64Tx}, {txn: payBase64Tx}, {txn: axferBase64Tx, signers: [] }])
The return of that call would be similar to what you’re expecting to get on signedTxs but the third position in the returned array will be set to null.

The signers param is there to let AlgoSigner know that specific transaction is not meant to be signed (since it’s a logicsig that’s gonna be signed externally).

Have a look at our documentation if you have any remaining doubts and feel free to reach out again.

1 Like

Hey janmarcano,

Thanks for the reply. So for the 3rd transaction that requires a logic signature account, can I still include it on the transaction group and send them all together below?

I just gave it a try as suggested and I’m getting an error: message: ‘If signing multiple transactions, they need to belong to a same group.’, code: 4200, name: ‘MultipleTxsRequireGroup’} when trying to run this code (specifically at await AlgoSigner.signTxn), Any ideas why?:

       // APPLICATION CALL TX
        let appArgs = [];
        const methodName = "buy";
        appArgs.push(utils.stringToUint8Array(methodName));
        let foreightAssets = [];

        let buyCallTx = await transactionWrapper.callApp(
            client,
            props.connectedAddress,
            undefined,
            +loadedSound['appID'],
            algosdk.OnApplicationComplete.NoOpOC,
            appArgs,
            foreightAssets,
            false);
        let buyCallBase64Tx = AlgoSigner.encoding.msgpackToBase64(buyCallTx.toByte());

        // PAYMENT TX BUYER -> SELLER
        let payTx = await transactionWrapper.pay(
            client,
            props.connectedAddress,
            loadedSound['owner'],
            +loadedSound['price'],
            undefined,
            false);
        let payBase64Tx = AlgoSigner.encoding.msgpackToBase64(payTx.toByte());

        // ASSET XFER: ESCROW -> BUYER
        let axferTx = await transactionWrapper.assetXfer(
            client,
            nftEscrowContract.escrowAddress,
            undefined,
            props.connectedAddress,
            loadedSound['owner'],
            1,
            undefined,
            +loadedSound['tokenID'],
            false);
        let axferBase64Tx = AlgoSigner.encoding.msgpackToBase64(axferTx.toByte());

        // ATOMIC TRANSFER of all txs in one
        let txns = [
            buyCallTx,
            payTx,
            axferTx
        ];

        try {
            let txgroup = algosdk.assignGroupID(txns);

            console.log('algo signing');
            let algoSignedTxs = await AlgoSigner.signTxn([
                { txn: buyCallBase64Tx },
                { txn: payBase64Tx },
                { txn: axferBase64Tx, signers: [] }]);
                
            // sign logic sig
            let signedAxferTx = algosdk.signLogicSigTransactionObject(axferTx, nftEscrowContract.lsigAccount).blob;
                
            let signedTxs = [
                algoSignedTxs[0],
                algoSignedTxs[1],
                signedAxferTx];
        
            console.log('attempting to send all signed txs...');
    
            let tx = undefined;
            try
            {
                tx = (await client.sendRawTransaction(signedTxs).do());
                console.log("Transaction : " + tx.txId);
            }
            catch(err)
            {
                console.log(err);
                throw err
            }
        
            // Wait for transaction to be confirmed
            const confirmedTxn = await algosdk.waitForConfirmation(client, tx.txId, 4);
            console.log("Transaction " + tx.txId + " confirmed in round " + confirmedTxn["confirmed-round"]);

It seems that you’re assigning the txs a groupId after they’ve been converted to base64, so you end up sending group-less transactions to AlgoSigner

1 Like

That did the trick thanks, so now I’m running this code:

            let txgroup = algosdk.assignGroupID(txns);

            let buyCallBase64Tx = AlgoSigner.encoding.msgpackToBase64(buyCallTx.toByte());
            let payBase64Tx = AlgoSigner.encoding.msgpackToBase64(payTx.toByte());
            let axferBase64Tx = AlgoSigner.encoding.msgpackToBase64(axferTx.toByte());

            console.log('algo signing');
            let algoSignedTxs = await AlgoSigner.signTxn([
                { txn: buyCallBase64Tx },
                { txn: payBase64Tx },
                { txn: axferBase64Tx, signers: [] }]);
                
            // sign logic sig
            console.log('lsig account signin');
            let signedAxferTx = algosdk.signLogicSigTransactionObject(axferTx, nftEscrowContract.lsigAccount).blob;
            console.log('algosigned txs: ');
            console.log(algoSignedTxs);
                
            // txs need to be in binary form (signedAxferTx already is?)
            let signedTxs = [
                AlgoSigner.encoding.base64ToMsgpack(algoSignedTxs[0].blob),
                AlgoSigner.encoding.base64ToMsgpack(algoSignedTxs[1].blob),
                signedAxferTx];

            console.log('signed txs:');
            console.log(signedTxs);

            let tx = undefined;
            try
            {
                tx = (await client.sendRawTransaction(signedTxs).do());
                console.log("Transaction : " + tx.txId);
            }
            catch(err)
            {
                console.log(err);
                throw err
            }

I do get the algosigner prompt to approve the 3 transactions and sign them, however when I’m sending them I’m getting an error related to logic sugnature (LogicSig.Logic version too new) See full error:

react_devtools_backend.js:4026 Error: Network request error. Received status 400: transaction {_struct:{} Sig:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Msig:{_struct:{} Version:0 Threshold:0 Subsigs:[]} Lsig:{_struct:{} Logic:[255 255 255 255 255 255 255 255 255 1] Sig:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Msig:{_struct:{} Version:0 Threshold:0 Subsigs:[]} Args:[]} Txn:{_struct:{} Type:axfer Header:{_struct:{} Sender:PHWSBNVBSV2HYJLCNEQ2DSMINU3DHH3EHZV743V4P7NCVQOKQC3C63PY24 Fee:{Raw:1000} FirstValid:22096466 LastValid:22097466 Note:[] GenesisID:testnet-v1.0 GenesisHash:JBR3KGFEWPEE5SAQ6IWU6EEBZMHXD4CZU6WCBXWGF57XBZIJHIRA Group:EYL6BCTNIY4PKLTPQ5J3ZTQIFUD3S7DI52LO5NN2M6YZ7W2TLOKA Lease:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] RekeyTo:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ} KeyregTxnFields:{_struct:{} VotePK:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] SelectionPK:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] StateProofPK:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] VoteFirst:0 VoteLast:0 VoteKeyDilution:0 Nonparticipation:false} PaymentTxnFields:{_struct:{} Receiver:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ Amount:{Raw:0} CloseRemainderTo:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ} AssetConfigTxnFields:{_struct:{} ConfigAsset:0 AssetParams:{_struct:{} Total:0 Decimals:0 DefaultFrozen:false UnitName: AssetName: URL: MetadataHash:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Manager:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ Reserve:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ Freeze:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ Clawback:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ}} AssetTransferTxnFields:{_struct:{} XferAsset:87793064 AssetAmount:1 AssetSender:DGNIWP77S7NBLRHPSNSSIZUJKH2IJM6BWULDHLFFIGOSBOI2DVUWXS2AUQ AssetReceiver:OCCAQU3ZWXEEOJR3SGXICBZR7GSLNBH5EA25WPEVTZ2E6RQDPXKZYRESXQ AssetCloseTo:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ} AssetFreezeTxnFields:{_struct:{} FreezeAccount:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ FreezeAsset:0 AssetFrozen:false} ApplicationCallTxnFields:{_struct:{} ApplicationID:0 OnCompletion:NoOpOC ApplicationArgs:[] Accounts:[] ForeignApps:[] ForeignAssets:[] LocalStateSchema:{_struct:{} NumUint:0 NumByteSlice:0} GlobalStateSchema:{_struct:{} NumUint:0 NumByteSlice:0} ApprovalProgram:[] ClearStateProgram:[] ExtraProgramPages:0} CompactCertTxnFields:{_struct:{} CertRound:0 CertType:0 Cert:{_struct:{} SigCommit:[] SignedWeight:0 SigProofs:{_struct:{} Path:[] HashFactory:{_struct:{} HashType:sha512_256} TreeDepth:0} PartProofs:{_struct:{} Path:[] HashFactory:{_struct:{} HashType:sha512_256} TreeDepth:0} Reveals:map[]}}} AuthAddr:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ} invalid : LogicSig.Logic version too new
    at w.<anonymous> (client.js:467:1)
    at t.emit (index.js:144:1)
    at t.onreadystatechange (client.js:802:1)

Could that be because I haven’t set up the txs array correctly maybe? This seems to work when I have the private key of the account hardcoded and send the atomic transfer as suggested by Atomic transfers - Algorand Developer Portal.

How did you generate nftEscrowContract.lsigAccount?
Can you print signedAxferTx in base64 and show it here?
Which network are you using mainnet/testnet/sandbox?

In the later case, is your sandbox up to date? See Pooled fees not working in sandbox - #2 by fabrice and Pooled fees not working in sandbox - #4 by fabrice

1 Like

Hello fabrice,

  1. I’m generating the lsigAccount like this:

From the client side I’m requesting the teal source code of the nft escrow contract (relies on token id and app id) from the server and then the server is returning the teal source as a string and looks something like this:

returned TEAL code:

#pragma version 4
global GroupSize
int 3
==
assert
gtxn 0 ApplicationID
int 87793081
==
assert
gtxn 1 TypeEnum
int pay
==
assert
gtxn 2 AssetAmount
int 1
==
assert
gtxn 2 XferAsset
int 87793064
==
assert
gtxn 2 Fee
int 1000
<=
assert
gtxn 2 AssetCloseTo
global ZeroAddress
==
assert
gtxn 2 RekeyTo
global ZeroAddress
==
assert
int 1
return

Then on the client side a class nftEscrowContract calculates the stateless smart contract related stuff such as lsigAccount and escrowAddress like this:

    // here this.escrowTealSource is the teal code retrieved from the server mentioned before
    async init()
    {
    try
        {
            this.escrowBytes = await this.calculateEscrowBytes(this.escrowTealSource);
            this.escrowAddress = await this.calculateEscrowAddress();
            return true;
        }
        catch(err)
        {
            console.log(err);
            return false;
        }
    }

    async calculateEscrowBytes(tealSource)
    {
        console.log('calculating escrow bytes...');

        let encoder = new TextEncoder();
        let programBytes = encoder.encode(tealSource);
        let compileResponse = await this.client.compile(programBytes).do();
        let escrowBytes = new Uint8Array(Buffer.from(compileResponse.result, "base64"));

        console.log('escrow bytes calculated: ' + escrowBytes);

        return escrowBytes;
    }

    async calculateEscrowAddress()
    {
        console.log('calculating escrow address...');
        this.lsigAccount = new algosdk.LogicSigAccount(this.escrowBytes);
        let escrowAddress = this.lsigAccount.address();
        console.log('escrow address calculated ' + escrowAddress);
        return escrowAddress;
    }

So I’m calculating lsigAccount like this:

this.lsigAccount = new algosdk.LogicSigAccount(this.escrowBytes);
  1. Printing signedAxferTx like this
console.log(Buffer.from(signedAxferTx.blob).toString("base64"));

returns:
gqRsc2lngaFsxAr///////////8Bo3R4boykYWFtdAGkYXJjdsQgcIQIU3m1yEcmO5GugQcx+aS2hP0gNds8lZ50T0YDfdWkYXNuZMQgGZqLP/+X2hXE75NlJGaJUfSEs8G1FjOspUGdILkaHWmjZmVlzQPoomZ2zgFS9zajZ2VurHRlc3RuZXQtdjEuMKJnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKjZ3JwxCDO6kV+H6vS+M9ea728TiVKW3HBg1c4KEXmpnYkFauIyqJsds4BUvseo3NuZMQgee0gtqGVdHwlYmkhocmIbTYzn2Q+a/5uvH/aKsHKgLakdHlwZaVheGZlcqR4YWlkzgU7nag=

  1. I’m using TESTNET using purestake’s client.

Something strange I just noticed, the escrow address generated on the server is different from the one generated on the client but they seemingly using the same source code :thinking: Any ideas what might be the issue?

Many thanks

So if you get your signed transaction and inspect it, you see the issue:

$ echo "gqRsc2lngaFsxAr///////////8Bo3R4boykYWFtdAGkYXJjdsQgcIQIU3m1yEcmO5GugQcx+aS2hP0gNds8lZ50T0YDfdWkYXNuZMQgGZqLP/+X2hXE75NlJGaJUfSEs8G1FjOspUGdILkaHWmjZmVlzQPoomZ2zgFS9zajZ2VurHRlc3RuZXQtdjEuMKJnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKjZ3JwxCDO6kV+H6vS+M9ea728TiVKW3HBg1c4KEXmpnYkFauIyqJsds4BUvseo3NuZMQgee0gtqGVdHwlYmkhocmIbTYzn2Q+a/5uvH/aKsHKgLakdHlwZaVheGZlcqR4YWlkzgU7nag=" | base64 -d > aa.tx

$ goal clerk inspect aa.tx
aa.tx[0]
{
  "lsig": {
    "l": "// unsupported version 18446744073709551615\n"
  },
  "txn": {
    "aamt": 1,
    "arcv": "OCCAQU3ZWXEEOJR3SGXICBZR7GSLNBH5EA25WPEVTZ2E6RQDPXKZYRESXQ",
    "asnd": "DGNIWP77S7NBLRHPSNSSIZUJKH2IJM6BWULDHLFFIGOSBOI2DVUWXS2AUQ",
    "fee": 1000,
    "fv": 22214454,
    "gen": "testnet-v1.0",
    "gh": "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
    "grp": "zupFfh+r0vjPXmu9vE4lSltxwYNXOChF5qZ2JBWriMo=",
    "lv": 22215454,
    "snd": "PHWSBNVBSV2HYJLCNEQ2DSMINU3DHH3EHZV743V4P7NCVQOKQC3C63PY24",
    "type": "axfer",
    "xaid": 87793064
  }
}

This is also very suspicious and seems to be the most likely explanation.

You see that

$ msgpacktool -d < aa.tx
{
  "lsig": {
    "l:b64": "////////////AQ=="
  },
  "txn": {
    "aamt": 1,
    "arcv:b64": "cIQIU3m1yEcmO5GugQcx+aS2hP0gNds8lZ50T0YDfdU=",
    "asnd:b64": "GZqLP/+X2hXE75NlJGaJUfSEs8G1FjOspUGdILkaHWk=",
    "fee": 1000,
    "fv": 22214454,
    "gen": "testnet-v1.0",
    "gh:b64": "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
    "grp:b64": "zupFfh+r0vjPXmu9vE4lSltxwYNXOChF5qZ2JBWriMo=",
    "lv": 22215454,
    "snd:b64": "ee0gtqGVdHwlYmkhocmIbTYzn2Q+a/5uvH/aKsHKgLY=",
    "type": "axfer",
    "xaid": 87793064
  }
}

show a very suspicious lsig.

So most likely there is a bug somewhere in the way you’re calling the API to compile.
I would debug and check what the call returns.

Note that when you run the API via curl, it works properly:

$ curl -s -H "X-API-Key: MY_KEY" https://testnet-algorand.api.purestake.io/ps2/v2/teal/compile -T aaa.teal -X POST
{"hash":"6SHVHHL6JEINGQKUFKXCH7ZLH7MKIOJ7FBWBYYJTHJM4C3OPXFP34H3ZKU","result":"BCABATIEgQMSRDMAGIG5u+4pEkQzARAiEkQzAhIiEkQzAhGBqLvuKRJEMwIBgegHDkQzAhUyAxJEMwIgMgMSRCJD"}

$ echo "BCABATIEgQMSRDMAGIG5u+4pEkQzARAiEkQzAhIiEkQzAhGBqLvuKRJEMwIBgegHDkQzAhUyAxJEMwIgMgMSRCJD" | base64 -d > aa.tok

$ goal clerk compile -D aa.tok
#pragma version 4
intcblock 1
global GroupSize
1 Like

For whatever reason the server was returning the compiled TEAL code but slightly altered, returning the binary version of the compiled code instead (and going through the lsig account signing) fixed this.

Thanks all!

1 Like