How to send transaction from web application?

Is it possible to send transactions directly from a web application?

I am using the algosdk 1.17.0 npm package in my web application. (Note that I have managed to get around the “buffer not defined” issue by using a poly-fill)

However, I am getting issues with sending transactions (POST endpoints). I have tried sending transactions using the AlgoExplorer endpoint for the testnet and my local sandbox node.

When I use Algod (v1) class, I get what appears to be a CORS error.

When I use AlgodV2 class, I get the same error.

I have also tried using a fetch request, but am unsure about how to structure the body and I do not think I will get a different response anyway.

It is OK to use a server/serverless function as a proxy between my web app and the algod node.
However, this can only be done for testnet/mainnet nodes (cannot connect to local nodes).

How do we send a transaction from a web application to our local sandbox node? Is it possible?

Hi @plasmatech8, like you said the error in your image originates from the CORS policy.

To send a transaction from a web application to our local sandbox node you simply set the parameters resolving to your local sandbox when you create the client, which seems you have done, however CORS blocks it.

I personally never had much success getting around CORS policies, but depending on your browser/language you will have to google the solution that works for you to disable it.

Do you have to use the local sandbox?
Alternatively you can connect to the TestNet:

 algod_address = "https://testnet-api.algonode.cloud"
 algod_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

I’ve not seen a cors issue using the sandbox alone.
Are you running on the same machine as the sandbox?
do you have some different hostname for the site you’re serving?
Is it definitely the Sandbox algod running on port 4001?
Have you changed any of your chrome settings?

Ben

Hi @plasmatech8, I’m getting the “buffer not defined” error, too. Can you share the polyfill solution that you used? I see a lot of different approaches on the Web, but if yours works, then it’s probably better to try yours first.

@Julia I had Buffer error also when using MyAlgoConnect, and I resolved it by adding thise lines:

import buffer from "buffer";
const { Buffer } = buffer;

if (!window.Buffer) window.Buffer = Buffer;

before importing and initializing MyAlgoConnect().
I also installed buffer via:
npm install --save buffer

@plasmatech8 sory for highjacking your thread :slight_smile:

Thank you, @gorazd. I found another workaround that was more specific to my use-case because your solution produced the window is undefined error. So I tried to dynamically load Buffer based on your code after the window object is loaded, but then the buffer not defined error occurred again. It seems the solution to this problem is heavily dependent upon which bundler the app is using. For these reasons, I don’t think Buffer should be used at all in the SDK because it can easily be replaced with Uint8Array, which is functionally equivalent and works in both Node and the browser contexts.

In my case, I’m using Vite; so the solutions above don’t work for reasons that I don’t have time to troubleshoot right now. But maybe they will work for others who are using Webpack.

1 Like

@gorazd @Julia haha, no problem. Good answer.

fyi, I am using the SvelteKit web framework (which uses Vite) and using this for my polyfill:

import { Buffer } from 'buffer';
import { browser } from '$app/env';
if (browser) window.Buffer = Buffer;

However, I recently found that I did not need it for MyAlgoConnect. I think this is because I set it up so that it only instantiates the MyAlgoConnect object in client-side (using the connect function in a class that I made) - so it avoids the Vite server-side rendering mechanism.

1 Like

Yes.
No.
Yes.
No.
Web application (using SvelteKit): http://localhost:3000/
Sandbox node: http://localhost:4001/swagger.json (works)
I am using the brave browser actually.

I deleted the code I was using and now I am getting an "path" has been externalized for browser compatibility and cannot be accessed in client code error from using functions in algosdk and I cannot remember out how I got around that problem…

But I have tried to re-do the code using fetch requests to get the same CORs error as before:

<script>
	async function sendRequest() {
		const MyAlgoConnect = (await import('@randlabs/myalgo-connect')).default;
		const algosdk = (await import('algosdk')).default;

		const algodToken = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
		const algodServer = 'http://localhost';
		const algodPort = 4001;

		// 1. Init clients

		// const client = new algosdk.Algodv2(algodToken, algodServer, algodPort);
		const myalgo = new MyAlgoConnect();

		// 2. Connect and get accounts

		const accounts = await myalgo.connect();
		console.log(accounts);

		// 3. Get suggested params

		// const sp = await client.getTransactionParams().do();
		// ^^^ Does not work due to error: "path" has been externalized
		// for browser compatibility and cannot be accessed in client code

		const spRes = await fetch(`${algodServer}:${algodPort}/v2/transactions/params`, {
			headers: {
				'X-Algo-API-Token': algodToken
			}
		});
		const spJson = await spRes.json();
		const sp = {
			fee: spJson['fee'],
			genesisHash: spJson['genesis-hash'],
			genesisID: spJson['genesis-id'],
			minFee: spJson['min-fee'],
			lastRound: spJson['last-round'],
			firstRound: spJson['last-round'] - 10 || 0
		};
		console.log(sp);

		// 4. Build transaction

		const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
			from: 'JROO4EICILSFPLNI7FMGAI3NX45ZP4B4TLQ4OK4N7D4CCR7QODEDVUYH7A',
			to: 'MK37YS6S7XI4L4LQ6SIO47W6KUP6RHWP6VPCTGZZWMKI4EBBPZU4ZVCWHE',
			amount: 1_000_000,
			suggestedParams: sp
		});
		console.log(txn);

		// 5. Sign transaction

		const txnSigned = await myalgo.signTransaction(txn.toByte());
		console.log(txnSigned);

		// 6. Send transaction

		// const res = await client.sendRawTransaction(txnSigned.blob).do();
		// ^^^ Does not work due to error: "path" has been externalized
		// for browser compatibility and cannot be accessed in client code

		const res = await fetch(`${algodServer}:${algodPort}/v2/transactions`, {
			method: 'POST',
			body: txnSigned.blob.toString(),
			headers: {
				'X-Algo-API-Token': algodToken
			}
		});
		console.log(res);
	}
</script>

<button class="btn" on:click={sendRequest}>Send Request</button>

I get the same result on testnet after changing the server, from/to address and removing port number.

I will need to take another look tomorrow.

1 Like

It looks like the spRes query is working properly, but sending the transaction fails.
I think the issue is that your /v2/transactions query fails with 400, and CORS may not be enabled on 400 errors.

I see you are not using fetch directly rather than using the SDK, because of some issue. Can you show the detailed issue you are seeing?

The SDK handles many operations that may be difficult to get right manually using fetch.
It is possible you are missing some headers: hdrs['Content-Type'] = 'application/x-binary';
It is also possible you should not use .toString().

Here is a full example with MyAlgoWallet and the SDK: GitHub - randlabs/myalgo-connect-example

1 Like

For this error, have you tried:
Vite: Module "path" has been externalized for browser compatibility - DEV Community or javascript - Allow electron `requires` to passthrough untouched in Vite - Stack Overflow (I’m assuming you are using vite, as this seems to pop up with vite)?
It is indeed much simpler to use the SDK.

Also, if you’re using vite, have you checked GitHub - fionnachan/vite-algosdk: vite & js-algorand-sdk template ?

1 Like

@fabrice thanks!

I installed path-browserify and I think it is working now.

I tried doing this before along (and a bunch of other things), didn’t see any changes then - but I might have assumed that the hot-reload was sufficient to see changes, where I really need to do a complete restart of my dev server.

In my svelte.config.js (which would be similar in an ordinary vite config)

		vite: {
			resolve: {
				alias: {
					path: 'path-browserify'
				}
			}
		}

It is also necessary to have the buffer polyfill mentioned above.

And this works now:

<script>
	async function sendRequest() {
		const MyAlgoConnect = (await import('@randlabs/myalgo-connect')).default;
		const algosdk = (await import('algosdk')).default;

		const algodToken = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
		const algodServer = 'https://node.testnet.algoexplorerapi.io';
		const algodPort = 443;

		// 1. Init clients

		const client = new algosdk.Algodv2(algodToken, algodServer, algodPort);
		const myalgo = new MyAlgoConnect();

		// 2. Connect and get accounts

		const accounts = await myalgo.connect();
		console.log(accounts);

		// 3. Get suggested params

		const sp = await client.getTransactionParams().do();
		console.log(sp);

		// 4. Build transaction

		const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
			from: 'UM4XZ727Q3AOO4M556GCSMINYSVU6VYU7OXTYUF4PKQD5HPGTMARUQGNAE',
			to: 'RDNV3TEDD4OFYCG2EKDGJTY26NG2RWJ2OEH3HFJAKGCTJM67J7FR43QV3Y',
			amount: 1_000_000,
			suggestedParams: sp
		});
		console.log(txn);

		// 5. Sign transaction

		const txnSigned = await myalgo.signTransaction(txn.toByte());
		console.log(txnSigned);

		// 6. Send transaction

		const res = await client.sendRawTransaction(txnSigned.blob).do();
		console.log(res);
	}
</script>

<button class="btn" on:click={sendRequest}>Send Request</button>

And also works in the sandbox:

const algodToken = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const algodServer = "http://localhost";
const algodPort = 4001;

Not 100% sure what the CORS issues was before, but it appears to be working for now which is good.

I will try it some more and see if it works as intended.

2 Likes