Sign the Transaction in the browser and send it in the backend

Hello,

I need something like this;
The user will sign the transaction in the browser, the signedtxn will be send with rest in the backend. The backend will send the transaction to the network.

How can I do that? Is there a tutorial on this topic?

Welcome to Algorand!

To pass signed and unsigned transactions between various components, just encode/decode them according to Encoding and Decoding - Algorand Developer Portal

Thanks for your reply fabrice.

This is how the transaction is created on the frontend.

const requestParams = [txnToSign];
    const request = formatJsonRpcRequest("algo_signTxn", requestParams);
    const result = await connector.sendCustomRequest(request);

    const parts: any[] = [];
    result.forEach((r: any, i: any) => {
      const rawSignedTxn = Buffer.from(r, "base64");
      parts.push(new Uint8Array(rawSignedTxn));
    });

    await fetch(`http://localhost:5000/transactions`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        data: algosdk.encodeObj(parts),
      }),
    })

I get an error when I do algosdk.decodeSignedTransaction(ctx.request.body.data) on the backend.

Error

error RangeError: Offset is outside the bounds of the DataView
    at DataView.getUint8 (<anonymous>)
...

In general, JSON.stringify something containing an Uint8Array is often an issue. At the very least, it explodes the resulting size.
I strongly recommend to never do that and instead encode in base64 any Uint8Array in JSON object.

Does this work after doing that?

Now I got an error like this @fabrice

Network request error. Received status 400: msgpack decode error [pos 1]: only encoded map or array can be decoded into a struct

When you submit the tx to the algod endpoint, you need to use directly the msgpack encoding. (no base64, no JSON, …). You also need to set up "Content-Type": "application/x-binary". (not JSON)
I also strongly recommend to always use the SDK to send queries to algod, rather than doing manual fetch to algod REST API.

@fabrice I already sent the transaction with SDK.

frontend;

const base64Arraybuffer = async (data) => {
    const base64url = await new Promise((r) => {
      const reader = new FileReader();
      reader.onload = () => r(reader.result);
      reader.readAsDataURL(new Blob([data]));
    });
    return base64url.split(",", 2)[1];
  };

let txn = algosdk.makePaymentTxnWithSuggestedParams(
      account,
      receiver,
      1000000,
      undefined,
      note,
      params
    );
    const txnToSign = [
      {
        txn: Buffer.from(algosdk.encodeUnsignedTransaction(txn)).toString(
          "base64"
        ),
        message: "Test transaction.",
      },
    ];
    const requestParams = [txnToSign];
    const request = formatJsonRpcRequest("algo_signTxn", requestParams);
    const result = await connector.sendCustomRequest(request);

    const parts: any[] = [];
    result.forEach((r: any, i: any) => {
      const rawSignedTxn = Buffer.from(r, "base64");
      parts.push(new Uint8Array(rawSignedTxn));
    });
    const test64 = await base64Arraybuffer(parts);

    await fetch(`http://localhost:1337/transactions/verify`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        data: test64,
      }),
    });

backend;

const myBuffer = Buffer.from(ctx.request.body.data, "base64");
const transaction = await algodclient
      .sendRawTransaction(myBuffer)
      .do();

Can you show us what ctx.request.body.data is?
Are you sure of base64Arraybuffer? A simpler implementation is to use Buffer and concatenate them (Buffer | Node.js v18.7.0 Documentation), and finally convert to base64 using toString.

Sure. ctx.request.body.data;

MTMwLDE2MywxMTUsMTA1LDEwMywxOTYsNjQsMTg2LDExOSwxNjIsMjQ1LDMwLDIwNiwzMywxODIsMTI1LDEyOCw5NCw4LDE1MCwxNDksNiw5NCwxNTQsNDMsMTU1LDQ0LDI4LDI1MSwxMzEsNiw3Miw1NSwyMTYsMjQ2LDE1NCw2NSwxMTgsMTQ0LDE0MSw5NywyNDYsODUsODEsMjMxLDE0MSwxMzgsMTM5LDIzOSwyNDcsMTQ3LDI0OCwyMTksMTU4LDI0OCwyMjksNDAsMTEsNDQsNTQsMjMwLDE3NSwxNTIsOTksMTE0LDIzMywyNCwxMTUsMTksMCwzLDE2MywxMTYsMTIwLDExMCwxMzgsMTYzLDk3LDEwOSwxMTYsMjA2LDAsMTUsNjYsNjQsMTYzLDEwMiwxMDEsMTAxLDIwNSwzLDIzMiwxNjIsMTAyLDExOCwyMDYsMSwxMDIsMjM3LDIzNiwxNjMsMTAzLDEwMSwxMTAsMTcyLDExNiwxMDEsMTE1LDExNiwxMTAsMTAxLDExNiw0NSwxMTgsNDksNDYsNDgsMTYyLDEwMywxMDQsMTk2LDMyLDcyLDk5LDE4MSwyNCwxNjQsMTc5LDIwMCw3OCwyMDAsMTYsMjQyLDQ1LDc5LDE2LDEyOSwyMDMsMTUsMTEzLDI0MCw4OSwxNjcsMTcyLDMyLDIyMiwxOTgsNDcsMTI3LDExMiwyMjksOSw1OCwzNCwxNjIsMTA4LDExOCwyMDYsMSwxMDIsMjQxLDIxMiwxNjQsMTEwLDExMSwxMTYsMTAxLDE5NiwxNiw4MywxMDgsOTcsMTE3LDEwMywxMDQsMTE2LDExNCwzMiw4MywxMDQsMTE3LDEwMiwxMDIsMTA4LDEwMSwxNjMsMTE0LDk5LDExOCwxOTYsMzIsODcsMjQ0LDE1OCwxOTAsNCw1NiwxNjcsNzYsMTQyLDEzOCw3MSw1OCw3NSwxOTgsNjUsMjIwLDE3NCwyNiwxMzgsMjExLDczLDM0LDIzLDcxLDI0OSw0NCwxOTYsNzUsMjMwLDE3OSwxLDI5LDE2MywxMTUsMTEwLDEwMCwxOTYsMzIsMjM5LDMzLDE0OSwxMjMsMjEzLDgzLDgwLDIyLDE1OSw1NiwxMzUsNzEsMTM0LDIyMSw2MywwLDIwMywxNywyNDYsNjEsMTM5LDE4Miw3MSwxMTksMTgyLDIzMCwyMDQsMTczLDEyNCw2LDc3LDExMSwxNjQsMTE2LDEyMSwxMTIsMTAxLDE2MywxMTIsOTcsMTIx

I can get the frontend array as follows. Then I don’t know how I can send the transaction with the right way.

const myBuffer = Buffer.from(ctx.request.body.data, "base64").toString();

The issue is most likely base64Arraybuffer

MTMwLDE2MywxMTUsMTA1LDEwMywxOTYsNjQsMTg2LDExOSwxNjIsMjQ1LDMwLDIwNiwzMywxODIsMTI1LDEyOCw5NCw4LDE1MCwxNDksNiw5NCwxNTQsNDMsMTU1LDQ0LDI4LDI1MSwxMzEsNiw3Miw1NSwyMTYsMjQ2LDE1NCw2NSwxMTgsMTQ0LDE0MSw5NywyNDYsODUsODEsMjMxLDE0MSwxMzgsMTM5LDIzOSwyNDcsMTQ3LDI0OCwyMTksMTU4LDI0OCwyMjksNDAsMTEsNDQsNTQsMjMwLDE3NSwxNTIsOTksMTE0LDIzMywyNCwxMTUsMTksMCwzLDE2MywxMTYsMTIwLDExMCwxMzgsMTYzLDk3LDEwOSwxMTYsMjA2LDAsMTUsNjYsNjQsMTYzLDEwMiwxMDEsMTAxLDIwNSwzLDIzMiwxNjIsMTAyLDExOCwyMDYsMSwxMDIsMjM3LDIzNiwxNjMsMTAzLDEwMSwxMTAsMTcyLDExNiwxMDEsMTE1LDExNiwxMTAsMTAxLDExNiw0NSwxMTgsNDksNDYsNDgsMTYyLDEwMywxMDQsMTk2LDMyLDcyLDk5LDE4MSwyNCwxNjQsMTc5LDIwMCw3OCwyMDAsMTYsMjQyLDQ1LDc5LDE2LDEyOSwyMDMsMTUsMTEzLDI0MCw4OSwxNjcsMTcyLDMyLDIyMiwxOTgsNDcsMTI3LDExMiwyMjksOSw1OCwzNCwxNjIsMTA4LDExOCwyMDYsMSwxMDIsMjQxLDIxMiwxNjQsMTEwLDExMSwxMTYsMTAxLDE5NiwxNiw4MywxMDgsOTcsMTE3LDEwMywxMDQsMTE2LDExNCwzMiw4MywxMDQsMTE3LDEwMiwxMDIsMTA4LDEwMSwxNjMsMTE0LDk5LDExOCwxOTYsMzIsODcsMjQ0LDE1OCwxOTAsNCw1NiwxNjcsNzYsMTQyLDEzOCw3MSw1OCw3NSwxOTgsNjUsMjIwLDE3NCwyNiwxMzgsMjExLDczLDM0LDIzLDcxLDI0OSw0NCwxOTYsNzUsMjMwLDE3OSwxLDI5LDE2MywxMTUsMTEwLDEwMCwxOTYsMzIsMjM5LDMzLDE0OSwxMjMsMjEzLDgzLDgwLDIyLDE1OSw1NiwxMzUsNzEsMTM0LDIyMSw2MywwLDIwMywxNywyNDYsNjEsMTM5LDE4Miw3MSwxMTksMTgyLDIzMCwyMDQsMTczLDEyNCw2LDc3LDExMSwxNjQsMTE2LDEyMSwxMTIsMTAxLDE2MywxMTIsOTcsMTIx

Indeed if you decode it you get:

$ base64 -D <<< MTMwLDE2MywxMTUsMTA1LDEwMywxOTYsNjQsMTg2LDExOSwxNjIsMjQ1LDMwLDIwNiwzMywxODIsMTI1LDEyOCw5NCw4LDE1MCwxNDksNiw5NCwxNTQsNDMsMTU1LDQ0LDI4LDI1MSwxMzEsNiw3Miw1NSwyMTYsMjQ2LDE1NCw2NSwxMTgsMTQ0LDE0MSw5NywyNDYsODUsODEsMjMxLDE0MSwxMzgsMTM5LDIzOSwyNDcsMTQ3LDI0OCwyMTksMTU4LDI0OCwyMjksNDAsMTEsNDQsNTQsMjMwLDE3NSwxNTIsOTksMTE0LDIzMywyNCwxMTUsMTksMCwzLDE2MywxMTYsMTIwLDExMCwxMzgsMTYzLDk3LDEwOSwxMTYsMjA2LDAsMTUsNjYsNjQsMTYzLDEwMiwxMDEsMTAxLDIwNSwzLDIzMiwxNjIsMTAyLDExOCwyMDYsMSwxMDIsMjM3LDIzNiwxNjMsMTAzLDEwMSwxMTAsMTcyLDExNiwxMDEsMTE1LDExNiwxMTAsMTAxLDExNiw0NSwxMTgsNDksNDYsNDgsMTYyLDEwMywxMDQsMTk2LDMyLDcyLDk5LDE4MSwyNCwxNjQsMTc5LDIwMCw3OCwyMDAsMTYsMjQyLDQ1LDc5LDE2LDEyOSwyMDMsMTUsMTEzLDI0MCw4OSwxNjcsMTcyLDMyLDIyMiwxOTgsNDcsMTI3LDExMiwyMjksOSw1OCwzNCwxNjIsMTA4LDExOCwyMDYsMSwxMDIsMjQxLDIxMiwxNjQsMTEwLDExMSwxMTYsMTAxLDE5NiwxNiw4MywxMDgsOTcsMTE3LDEwMywxMDQsMTE2LDExNCwzMiw4MywxMDQsMTE3LDEwMiwxMDIsMTA4LDEwMSwxNjMsMTE0LDk5LDExOCwxOTYsMzIsODcsMjQ0LDE1OCwxOTAsNCw1NiwxNjcsNzYsMTQyLDEzOCw3MSw1OCw3NSwxOTgsNjUsMjIwLDE3NCwyNiwxMzgsMjExLDczLDM0LDIzLDcxLDI0OSw0NCwxOTYsNzUsMjMwLDE3OSwxLDI5LDE2MywxMTUsMTEwLDEwMCwxOTYsMzIsMjM5LDMzLDE0OSwxMjMsMjEzLDgzLDgwLDIyLDE1OSw1NiwxMzUsNzEsMTM0LDIyMSw2MywwLDIwMywxNywyNDYsNjEsMTM5LDE4Miw3MSwxMTksMTgyLDIzMCwyMDQsMTczLDEyNCw2LDc3LDExMSwxNjQsMTE2LDEyMSwxMTIsMTAxLDE2MywxMTIsOTcsMTIx
130,163,115,105,103,196,64,186,119,162,245,30,206,33,182,125,128,94,8,150,149,6,94,154,43,155,44,28,251,131,6,72,55,216,246,154,65,118,144,141,97,246,85,81,231,141,138,139,239,247,147,248,219,158,248,229,40,11,44,54,230,175,152,99,114,233,24,115,19,0,3,163,116,120,110,138,163,97,109,116,206,0,15,66,64,163,102,101,101,205,3,232,162,102,118,206,1,102,237,236,163,103,101,110,172,116,101,115,116,110,101,116,45,118,49,46,48,162,103,104,196,32,72,99,181,24,164,179,200,78,200,16,242,45,79,16,129,203,15,113,240,89,167,172,32,222,198,47,127,112,229,9,58,34,162,108,118,206,1,102,241,212,164,110,111,116,101,196,16,83,108,97,117,103,104,116,114,32,83,104,117,102,102,108,101,163,114,99,118,196,32,87,244,158,190,4,56,167,76,142,138,71,58,75,198,65,220,174,26,138,211,73,34,23,71,249,44,196,75,230,179,1,29,163,115,110,100,196,32,239,33,149,123,213,83,80,22,159,56,135,71,134,221,63,0,203,17,246,61,139,182,71,119,182,230,204,173,124,6,77,111,164,116,121,112,101,163,112,97,121%

which indicates incorrect encoding: it’s encoding the JSON array matching Uint8Array instead of encoding the raw bytes.

I’m letting you figure out how to fix the function, but please post the solution afterwards to help others.

If I can solve the problem, of course, I will share the solution here. But right now I didn’t know how to do it.

Hi. @fabrice I think I’ve made progress. I am currently getting an error like this.

'transaction {_struct:{} Sig:[130 163 115 105 103 196 64 248 125 236 157 39 64 85 26 203 151 56 222 99 156 170 63 112 240 48 65 38 191 200 190 191 204 218 184 98 237 42 222 116 48 60 79 115 20 179 171 9 78 96 222 213 240 222 161 55 72 233 103 33 25 20 187 146] Msig:{_struct:{} Version:0 Threshold:0 Subsigs:[]} Lsig:{_struct:{} Logic:[] 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: Header:{_struct:{} Sender:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ Fee:{Raw:0} FirstValid:0 LastValid:0 Note:[] GenesisID: GenesisHash:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Group:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 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:0 AssetAmount:0 AssetSender:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ AssetReceiver:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ 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 : unknown tx type '

I’ve gone into detail in this video on how we achieve passing transactions between the client and server in JSON at Polynize.io. Polynize Rewards Technical Overview - YouTube Hope this helps.

Also, this issue has all the relevant code. Atomic transfers - Invalid Input: Unable to parse transaction · Issue #10 · perawallet/connect · GitHub