Atomics transfer through http request

Hey guys,

I am trying to do a bit of a fancy atomic transfer.
Basically I want two transactions to be signed in the browser, and 2 transactions to be signed in the backend. And I want those 4 transactions to be atomic.

Reason is that I want a person to be able to sign the first transactions through the browser and a wallet plugin directly, without getting the private key in my own backend.
And sign the two others with the app secret key, so absolutely not in the browser.

So what I do is that, I create both first transactions in the frontend, I group them, save the group generated, and signed them.

const optInTxn = algosdk.makeAssetTransferTxnWithSuggestedParams(
                buyerAddress,
                buyerAddress,
                undefined,
                undefined,
                0,
                new Uint8Array(0),
                assetId,
                params
            )
            const algoFill = algosdk.makePaymentTxnWithSuggestedParams(
                buyerAddress,
                escrowAccount,
                price * 1000000,
                undefined,
                new Uint8Array(0),
                params
            )
            algosdk.assignGroupID([algoFill, optInTxn])

            const signedTxn = await myAlgoWallet.signTransaction([
                optInTxn.toByte(),
                algoFill.toByte(),
            ])

Then I pass the blob I get from signedTxn[0] and signedTxn[1] to the backend.
I pass the group I get from algoFill.group to the backend too.
And the params by the way, since I need the same for all (kinda).

Then in the backend, I do

        const appArgs = []
        appArgs.push(new Uint8Array(Buffer.from('buy')))
        const callTxn = algosdk.makeApplicationNoOpTxn(
            PLATEFORM_ADDR,
            params,
            appId,
            appArgs
        )
        const assetTransfer = algosdk.makeAssetTransferTxnWithSuggestedParams(
            escrowAccount,
            buyerAddress,
            undefined,
            undefined,
            1,
            new Uint8Array(0),
            assetId,
            params
        )

        callTxn.group = new Buffer.from(group)
        assetTransfer.group = new Buffer.from(group)

        const signedCall = callTxn.signTxn(plateformSK)

        const lsig = new algosdk.LogicSigAccount(
            new Uint8Array(Buffer.from(escrowSign, 'base64'))
        )
        const signedAsset = algosdk.signLogicSigTransaction(assetTransfer, lsig)

        const signed = [
            signedCall,
            new Uint8Array(algoFillSigned),
            new Uint8Array(optInSigned),
            signedAsset.blob,
        ]

        const createTxId = await algodClient.sendRawTransaction(signed).do()
        await waitTransaction(createTxId.txId, 2)

And then the error I get :

'{"message":"TransactionPool.Remember: transactionGroup: incomplete group: KUGOVUOHYQTCWJKPKDTNZKBXALNVGT5OXPLNMNU3KE3S4LIGRCHQ != KPXWCVII4W7ROXS2UV4H4NMRMYJ5GSCUIJRA3F6PK74LLICHHARQ ({{} [E2JSYIOCTVYK67K7AANTHILFLIHJN6ZHDSLH627RHT2PEAW5LMXA 43KMZ5L46DN6LV6V7SGYU54CS5KWXWNYHKYLKNC7AAVXSSNJ3WOQ]})"}\n'

For infos, I am passing those data as HTTPS POST.

I tried nearly EVERYTHING. And so if I group only the 2 backend ones, it works. If I group only the two front ones it work. So then I tried to assignGroupId from the browser for the 4, and signed the 2 missing signature in the backend, couldn’t make it work.
And when I do the 4 in the backend or the 4 in the browser it works too.

So what can I do ? What am I doing it wrong ?
Or maybe there is a better way to achieve what I want ?

Thanks you very much for the help guys.

Cheers

You need to generate all transactions either on the backend or the frontend (I would personally do it on the backend, it’s a bit simpler).
Then group all 4 transactions.

Then only you can have them sent to frontend/backend and signed.

The way you are doing it, you are only grouping the 2 frontend transactions:

            algosdk.assignGroupID([algoFill, optInTxn])

To transmit transactions, use msgpack or base64 msgpack, as algosigner/ARC-1 does:
algosigner/dApp-integration.md at develop · PureStake/algosigner · GitHub / ARCs/arc-0001.md at main · algorandfoundation/ARCs · GitHub

Ok I thought about that but I was hoping this would work.

I understand then, thanks a lot Fabrice!

Cheers

EDIT :

So I just tried, I had an object issue.

I got the same problem. Same error. This time doing

const optInTxn = algosdk.makeAssetTransferTxnWithSuggestedParams(
                this.state.walletAddress,
                this.state.walletAddress,
                undefined,
                undefined,
                0,
                new Uint8Array(0),
                this.state.art.id,
                params
            )
            const algoFill = algosdk.makePaymentTxnWithSuggestedParams(
                this.state.walletAddress,
                this.state.art.address,
                this.state.art.price * 1000000,
                undefined,
                new Uint8Array(0),
                params
            )

            const appArgs = []
            appArgs.push(new Uint8Array(Buffer.from('buy')))
            const callTxn = algosdk.makeApplicationNoOpTxn(
                'ZGCNY3TDLVOJLIMSGR5VODU5R2UEQ5IDC4JCVALKXVVM6CZ7KZWIE7UUL4',
                params,
                43973597,
                appArgs
            )

            const assetTransfer = algosdk.makeAssetTransferTxnWithSuggestedParams(
                this.state.art.address,
                this.state.walletAddress,
                undefined,
                undefined,
                1,
                new Uint8Array(0),
                this.state.art.id,
                params
            )

            algosdk.assignGroupID([algoFill, optInTxn, callTxn, assetTransfer])

            const signedTxn = await myAlgoWallet.signTransaction([
                optInTxn.toByte(),
                algoFill.toByte(),
            ])

Then passing

optInTxn: Array.from(signedTxn[0].blob),
                algoFillTxn: Array.from(signedTxn[1].blob),
                callTxn: Array.from(algosdk.encodeUnsignedTransaction(callTxn)),
                assetTransfer: Array.from(algosdk.encodeUnsignedTransaction(assetTransfer)),

To the backend

And then

const plateformSK = algosdk.mnemonicToSecretKey(PLATEFORM_MNEMONIC).sk

        const callTxnDecoded = algosdk.decodeUnsignedTransaction(new Uint8Array(callTxn))
        const signedCall = callTxnDecoded.signTxn(plateformSK)

        const signedAssetDecoded = algosdk.decodeUnsignedTransaction(new Uint8Array(assetTransfer))
        const lsig = new algosdk.LogicSigAccount(
            new Uint8Array(Buffer.from('BCADAAHd9/sUgQUzABkSQAAKIjMAGRJAABkiQzIEgQISMwAQgQYSEDMBECMSEEAABCJDI0MzABgkEjMBIDIDEhBBACgjQzMAGCQSMwAgMgMSEDMBIDIDEhAzAiAyAxIQMwMgMgMSEEEAAiNDIkM=', 'base64'))
        )
        const signedAsset = algosdk.signLogicSigTransaction(signedAssetDecoded, lsig)

        const signed = [
            signedCall,
            new Uint8Array(algoFillSigned),
            new Uint8Array(optInSigned),
            signedAsset.blob,
        ]

        const createTxId = await algodClient.sendRawTransaction(signed).do()
        await waitTransaction(createTxId.txId, 2)

In the backend, same error.

Actually might not be related to group or something … ?

Thanks again !

I am spamming a bit, but it seems it was an issue of ordering.

In the sendRawTranscation call, I was sending in a wrong order.

Seems to be working. I’ll be going forward a bit myself.

Thanks for your help anyway, I hope I’ll find my way by myself.

Cheers

If you’re using JSON, encoding byte arrays often creates issues.
Converting first the byte arrays to base64 is often a good idea.

OK, thanks for the info !
I’ll try that for sure then, thanks for your time