How to fire EthersJS *.on events? - reactjs

I am working on a react app with redux. I did implement thunk actions to:
Create a web3modal instance
Register a ethersjs provider
Register a ethersjs signer
Everything is very basic and simple.
However whenever I try to make use of the on events (doesn't matter if provider oder contract), they simply won't fire.
I have 2 files:
walletSlice.ts which will handle all the redux action and reducer logic with #reduxjs/toolkit .
wallet-api.ts which has all the relevant functions to interact with the wallet.
The walletSlice.ts relevant part looks exactly like this:
export const connectWallet = createAsyncThunk(
'web3wallet/connectWallet',
async (arg, thunkApi) => {
const instance = await WalletAPI.registerWalletInstance();
provider = await WalletAPI.registerWalletProvider(instance);
signer = await WalletAPI.registerWalletSigner(provider);
return Promise.resolve();
}
);
The wallet-api.ts relevant parts look exactly like this:
import { ethers, providers } from 'ethers';
import Web3Modal from 'web3modal';
// get Web3Modal Instance
export async function registerWalletInstance(): Promise<Web3Modal> {
const providerOptions = {};
const web3Modal = new Web3Modal({
providerOptions,
});
const instance = await web3Modal.connect();
return Promise.resolve(instance);
}
/**
* register Wallet provider.
* Events on provider #see https://docs.ethers.io/v5/api/providers/provider/#Provider--event-methods
* Implementing the EIP-1193 Standard #see https://eips.ethereum.org/EIPS/eip-1193
*/
export async function registerWalletProvider(
instance: any
): Promise<providers.JsonRpcProvider> {
const provider = new ethers.providers.Web3Provider(instance);
// Subscribe to accounts change
provider.on('accountsChanged', (accounts: string[]) => {
console.log(accounts);
});
// Subscribe to chainId change
provider.on('chainChanged', (chainId: number) => {
console.log(chainId);
});
// Subscribe to provider connection
provider.on('connect', (info: { chainId: number }) => {
console.log(info);
});
// Subscribe to provider disconnection
provider.on('disconnect', (error: { code: number; message: string }) => {
console.log(error);
});
provider.on('error', (tx) => {
// Emitted when any error occurs
console.log({ tx });
});
return Promise.resolve(provider);
}
// register Wallet signer.
export async function registerWalletSigner(
provider: providers.JsonRpcProvider
): Promise<providers.JsonRpcSigner> {
const signer = provider.getSigner();
return Promise.resolve(signer);
}
None of the provider.on() events will fire. I've tried to change networks from rinkeby to polygon or mainnet, but nothing happens. When I disconnect from the site, nothing happens. It is the same with all provider events as shown in wallet-api.ts. I did try the same approach with another file called contract-api.ts. However the contract events won't fire either.
I tried to use the provider.on() events with useEffect() or useCallback(). Moved the code to a standalone tsx. But nothing happened.

await web3Modal.connect() already returns a provider;
try to use that one instead of new ethers.providers.Web3Provider(instance);

Related

How to use Apollo-client hook useQuery inside another function?

I am trying to make a module for web3 authentication. For that I have a function which gets user's public address and makes a GraphqQL query to the back-end to see if it's present in the database.
export const useWeb3 = async () => {
const [ user, setUser ] = useState({})
if (!(window as any).ethereum) {
window.alert('Please install MetaMask first.');
return;
}
let web3: Web3 | undefined = undefined
if (!web3) {
try {
// Request account access if needed
await (window as any).ethereum.enable();
// We don't know window.web3 version, so we use our own instance of Web3
// with the injected provider given by MetaMask
web3 = new Web3((window as any).ethereum);
} catch (error) {
window.alert('You need to allow MetaMask.');
return [user];
}
}
const coinbase = await web3.eth.getCoinbase()
if (!coinbase) {
window.alert('Please activate MetaMask first.')
return [user]
}
console.log("COINBASE", coinbase)
const publicAddress = coinbase.toLowerCase();
// setLoading(true);
const { data } = useQuery(QUERY_USER_BY_PUBLIC_ADDRESS, {
variables: { publicAddress }
})
if(data) setUser(data)
return [user]
}
So I have a custom hook useWeb3(). I made it a hook so I could call useQuery inside it. I import useWeb3 inside the other component and when I try to use it like that:
const [user] = useWeb3()
It says Type
'Promise<{}[] | undefined>' is not an array type.ts(2461)
Welcome.component.tsx(34, 11): Did you forget to use 'await'?
The question is how can I implement the logic of taking public address and query back end with that inside a separate function so I could use it in other components?
If you just want to return the QUERY_USER_BY_PUBLIC_ADDRESS response data and aren't looking to have the results rendered as part of a React component, then you don't need to use the useQuery React hook here. Just use Apollo Client's query function directly, which returns a Promise:
...
// `client` here is your instantiated `ApolloClient` instance
const result = await client.query({
query: QUERY_USER_BY_PUBLIC_ADDRESS,
variables: { publicAddress }
});
...

How to connect truffle contract with ReactJS?

I have made a lottery contract and now I want to connect it to the front-end which I will create with ReactJS. I have used Truffle framework for the deployment and tests for my contract.
npm i #truffle/contract
Then in utils/loadContract:
import contract from "#truffle/contract"
export const loadContract = async (name, provider) => {
// in truffle, contracts are in "contracts" directory
const res = await fetch(`/contracts/${name}.json`)
const Artifact = await res.json()
const _contract = contract(Artifact)
_contract.setProvider(provider)
let deployedContract = null
try {
deployedContract = await _contract.deployed()
} catch {
console.error("You are connected to the wrong network")
}
return deployedContract
}
Then load the contract inside app.jsx. First:
npm i #metamask/detect-provider
then write your code inside app.js
import detectEthereumProvider from '#metamask/detect-provider'
import { loadContract } from "./utils/load-contract";
import {useEffect, useState } from "react";
// in useEffect I load this state. So when your component is loaded, you would have access to web3Api.contract to call the contract methods
const [web3Api, setWeb3Api] = useState({
provider: null,
isProviderLoaded: false,
web3: null,
contract: null
})
// call this function inside useEFfect, if user connects to different account, you will update it
const setAccountListener = provider => {
provider.on("accountsChanged", _ => window.location.reload())
provider.on("chainChanged", _ => window.location.reload())
}
// useEffect is called before your component loaded
// load the contract, set the state, so when your compoent loaded, your state will be ready
useEffect(() => {
const loadProvider = async () => {
// if Metamask installed, this will detect its provider
const provider = await detectEthereumProvider()
// load the contract if provider exists
if (provider) {
// Lottery is not name of the file, it is NAME OF CONTRACT
const contract = await loadContract("Lottery", provider)
setAccountListener(provider)
setWeb3Api({
web3: new Web3(provider),
provider:provider,
// Now you set the contract.
// when your app.js loaded you will be able to call contract methods
contract:contract,
isProviderLoaded: true
})
} else {
setWeb3Api(api => ({...api, isProviderLoaded: true}))
console.error("Please, install Metamask.")
}
}
loadProvider()
}, [])

Solana React TypeError: this.wallet.signTransaction is not a function

Now I'm going to connect solana and React.js.
And I tried to get wallet like this.
import { useConnection, useWallet } from '#solana/wallet-adapter-react';
....
function Header(props) {
const wallet = useWallet();
const initialize = async () => {
const provider = await getProvider()
const program = new Program(contractJson, programID, provider);
console.log('program', program)
try {
/* interact with the program via rpc */
await program.rpc.initialize({
accounts: {
myOntology: provider.wallet.publicKey,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
});
} catch (err) {
console.log("Transaction error: ", err);
}
}
}
when I call initialize method, I got this error.
TypeError: this.wallet.signTransaction is not a function
So I've logged about wallet and signTransaction is null.
enter image description here
How can I solve this problem?
The provider you pass into Program needs to incorporate your wallet
const wallet = useWallet();
const connection = new Connection('<devnet url here>')
const provider = new anchor.Provider(connection, wallet, anchor.Provider.defaultOptions())
const program = new Program(contractJson, programID, provider);
You're using an anchor rpc program method, so you need to invoke the useAnchorWallet method.
import { useAnchorWallet } from "#solana/wallet-adapter-react";
const wallet = useAnchorWallet();
If you look at the useAnchorWallet method it contains an interface which expects a Transaction as an input:
signTransaction(transaction: Transaction): Promise<Transaction>;
The standard wallet adapter has the same method namespace but has a different interface:
signTransaction: SignerWalletAdapterProps['signTransaction'] | undefined;

How to setup a function which gets app settings and sets it as localStorage before the page loads. (next.js)

I've been working on a Next.JS web application for the past couple of days but I've reached a problem. The app has an API call (/api/settings) which returns some settings about the application from the database. Currently, I have a function which returns these settings and access to the first component:
App.getInitialProps = async () => {
const settingsRequest = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/settings`
);
const settingsResponse = await settingsRequest.json();
return { settings: settingsResponse };
};
This does work and I am able to pass in settings to components but there are two problems with this:
I need to nest the prop through many components to reach the components that I need
This request runs every time a page is reloaded/changed
Essentially, I need to create a system that does this:
runs a function in the _app.tsx getInitialProps to check if the data is already in localStorage, if not make the API request and update localStorage
have the localStorage value accessible from a custom hook.
Right now the problem with this is that I do not have access to localStorage from the app.tsx getInitialProps. So if anyone has an alternative to run this function before any of the page loads, please let me know.
Thanks!
I found a solution, it might be a janky solution but I managed to get it working and it might be useful for people trying to achieve something similar:
First we need to create a "manager" for the settings:
export const checkIfSettingsArePresent = () => {
const settings = localStorage.getItem("app_settings");
if (settings) return true;
return false;
};
export const getDataAndUpdateLocalStorage = async () => {
const r = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/settings`);
const response = await r.json();
localStorage.setItem("app_settings", JSON.stringify(response));
};
With that created we can add a UseEffect hook combined with a useState hook that runs our function.
const [doneFirst, setDoneFirst] = useState<boolean>(false);
useEffect(() => {
const settingsPreset = checkIfSettingsArePresent();
if (performance.navigation.type != 1)
if (settingsPreset) return setDoneFirst(true);
const getData = async () => {
await getDataAndUpdateLocalStorage();
setDoneFirst(true);
};
getData();
}, []);
//any other logic
if (!doneFirst) {
return null;
}
The final if statement makes sure to not run anything else before the function.
Now, whenever you hot-reload the page, you will see that the localStorage app_settings is updated/created with the values from the API.
However, to access this more simply from other parts of the app, I created a hook:
import { SettingsType } from "#sharex-server/common";
export default function useSettings() {
const settings = localStorage.getItem("app_settings") || {
name: "ShareX Media Server",
};
//#ts-ignore
return JSON.parse(settings) as SettingsType;
}
Now I can import useSettings from any function and have access to my settings.

Phoenix + React Native and Redux: Where should I put channel object?

I have a component called PlatformMain that currently depends on a global channel object from Phoenix defined inside of the component's file.
let channel;
let socket = new Socket("...", {params: {token: window.userToken}});
socket.connect();
class PlatformMain extends React.Component {
componentWillMount() {
this.connectUser();
}
connectUser() {
const { user } = this.props;
channel = socket.channel("user_pool:" + user.email, { app: APP });
this.setupChannel();
}
setupChannel() {
channel.join()
.receive("ok", () => { console.log("Successfully joined call channel") })
.receive("error", () => { console.log("Unable to join") })
channel.on("match_found", payload => {
...
});
...
}
If the user presses a button, I'd like that to dispatch an action as well as push a message to the channel.
onPress() {
console.log("APPROVE_MATCH");
const { peer, waitForResponse } = this.props;
approveMatch(peer);
channel.push("approve_match", { // <------ want to put this somewhere else
"matched_client_email": peer.email,
});
}
My question is, if I want to "reduxify" the channel.push call, where should I put it? It feels weird not having the channel.push(...) somewhere else since it's an API call. I was going to put it in a saga using redux-saga like so:
function* approveMatch(action) {
const peer = action.payload.peer;
channel.push("approve_match", { // <------- but how would I get the same channel object?
"matched_client_email": peer.email,
});
}
export default function* watchMatchingStatus() {
yield takeEvery(matchingStatusActions.APPROVE_MATCH, approveMatch);
}
But wouldn't I need to point to the same channel object? How would I do that? If I put the initialization of the channel in its own file and I export and import it in multiple places, wouldn't it execute the file multiple times (and consequently join the channel multiple times)?
You could put the initialization of channel in its own file and safely import multiple times, the evaluation of the module only happens once. You can check the spec for confirmation:
Do nothing if this module has already been evaluated. Otherwise, transitively evaluate all module dependences of this module and then evaluate this module.

Resources