I have a react project I am running at http:\localhost:3000
which connects to ganache running at http:\localhost:7545
.
I deploy a small smart contract onto ganache which increments a counter and emits an event, like this…
// SPDX-License-Identifier: MIT pragma solidity >0.8.0; contract Pong { uint public pong_count; function ping() public returns (uint){ pong_count++; emit Pinged(this, pong_count); return pong_count; } event Pinged(Pong indexed me, uint count); function pong() public returns (uint){ pong_count++; emit Ponged(this, pong_count); return pong_count; } event Ponged(Pong indexed me, uint count); }
I want to listen to the Pinged
and Ponged
events from my contract.
As this is just a helper project for something else I am working on I embed the private key from a ganache account and make an account from it…
var pk_account = w3.eth.accounts.privateKeyToAccount(pk);
Then elsewhere in my code I create an instance of my Pong contract called ponger
and store it on a context object in my react app, and invoke the ping() contract method using send
like this…
ctx.ponger.methods .ping() .send({from:ctx.pk_account.address}) .then((result,err)=>{ if (err){ console.log("pong error: ", err); } else { console.log("pong : ", result); result.gas = 200000; ctx.pk_account.signTransaction(result, sent); } });
This works like a charm and the local callback sent
gets invoked correctly.
I add event listeners to my ponger
instance…
function ev_Pong(ev, err){ console.log("got pong event", ev); } function ev_Ping(ev, err){ console.log("got ping event", ev); } ctx.ponger.events.Pinged(ev_Ping); ctx.ponger.events.Ponged(ev_Pong);
This is where the fun starts. The message I receive back in ev_Ping
is…
got ping event Error: The current provider doesn't support subscriptions: HttpProvider at subscription.js:176:1
So, duh, I need to use websockets instead of HTTP, right? That means I just connect to ws:\localhost:7545
instead of http:\localhost:7545
.
(Aside: I do not have any of these issues if I use MetaMask to deliver me web3…)
However I then get a CORS error like this…
Access to XMLHttpRequest at 'ws://localhost:7545/' from origin 'http://localhost:3000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.
So my question is how do I overcome the CORS error? I am not sure if this is a ganache question or an old fashioned CORS question, or what.
I don’t want to give up on event listening and having to parse through event logs just yet – although I realise there probably is a long route to a different solution.
Advertisement
Answer
I figured out what was wrong and it was my fault. The web3 websocket provider does not enforce CORS so it can be used in my use case.
The issue I was having was that even though I change the protocol in the URI I sent web3, I was still asking for an HTTPProvider
instead of a WebsocketProvider
.
I made a few changes to getWeb3.js for my purposes like this…
const getWeb3 = (url) => { ... if (url){ if (url.substring(0,2) === 'ws'){ const provider = new Web3.providers.WebsocketProvider(url); const web3 = new Web3(provider); console.log("Using WS URL for web3: ", url); resolve(web3); } else { const provider = new Web3.providers.HttpProvider(url); const web3 = new Web3(provider); console.log("Using HTTP URL for web3: ", url); resolve(web3); } } ... }
This worked fine.
On the plus side it made me understand CORS a lot better. I really don’t like disabling anything in the browser.