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.
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.