Web3 error when trying to call contract methods - reactjs

I encounter the following error while trying to run a dapp made using React, Truffle & Ganache. I'm also using web3 version 1.7.1. The problem is that the code enters in a catch(error) sequence of a try/catch and then displays what it is intended correctly. Why is this happening and why does the following error appears?
index.js:1 TypeError: Cannot read properties of undefined (reading 'methods')
at HomePage.componentDidMount
What the code should do: Display something like "Address is: 0x0D05b3220E9cC7A90623fc506cEB64Ab885FD6C6"
What the code does: It shows me the prompt "Failed to load web3, accounts, or contract. Check console for details" AND THEN "Address is: 0x0D05b3220E9cC7A90623fc506cEB64Ab885FD6C6"
The code is the following:
import React, { Component } from "react";
import MySmartContract from "../contracts/MySmartContract.json";
import getWeb3 from "../getWeb3";
//Importing components
class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
ContractInstance: undefined,
account: null,
web3: null,
isOwner: false
}
}
componentDidMount = async () => {
// For refreshing the page a single time
// so that web3 and instance is loaded every time
if (!window.location.hash) {
window.location = window.location + '#loaded';
window.location.reload();
}
try {
// Get network provider and web3 instance.
const web3 = await getWeb3();
// Use web3 to get the user's accounts.
const accounts = await web3.eth.getAccounts();
// Get the contract instance.
const networkId = await web3.eth.net.getId();
const deployedNetwork = MySmartContract.networks[networkId];
const instance = new web3.eth.Contract(
MySmartContract.abi,
deployedNetwork && deployedNetwork.address,
);
// Set web3, accounts, and contract to the state, and then proceed with an
// example of interacting with the contract's methods.
this.setState({ ContractInstance: instance, web3: web3, account: accounts[0] });
const owner = await this.state.ContractInstance.methods.getOwnerAddress().call();
if (this.state.account === owner) {
this.setState({ isOwner: true });
}
} catch (error) {
// Catch any errors for any of the above operations.
alert(
`Failed to load web3, accounts, or contract. Check console for details.`,
);
console.error(error);
}
};
render() {
if (!this.state.web3) {
return (
<h1>
Loading Web3, accounts and contract...
</h1>
)
}
return (
<div><h1>Address is: {this.state.account}</h1></div>
)
}
}
export default HomePage;
The content of getWeb3.js is the following:
import Web3 from "web3";
const getWeb3 = () =>
new Promise((resolve, reject) => {
// Wait for loading completion to avoid race conditions with web3 injection timing.
window.addEventListener("load", async () => {
// Modern dapp browsers...
if (window.ethereum) {
const web3 = new Web3(window.ethereum);
try {
// Request account access if needed
await window.ethereum.enable();
// Accounts now exposed
resolve(web3);
} catch (error) {
reject(error);
}
}
// Legacy dapp browsers...
else if (window.web3) {
// Use Mist/MetaMask's provider.
const web3 = window.web3;
console.log("Injected web3 detected.");
resolve(web3);
}
// Fallback to localhost; use dev console port by default...
else {
const provider = new Web3.providers.HttpProvider(
"http://127.0.0.1:8545"
);
const web3 = new Web3(provider);
console.log("No web3 instance injected, using Local web3.");
resolve(web3);
}
});
});
export default getWeb3;

This code seems to be wrong!
this.setState({ ContractInstance: instance, web3: web3, account: accounts[0] });
const owner = await this.state.ContractInstance.methods.getOwnerAddress().call();
You should not try to use state value as soon as you set up.
So you need to call your function in the second line like this:
const owner = await instance.methods.getOwnerAddress().call();
Injected connector code sample
const ConnectToInjected = async () => {
let provider = null;
if (typeof window.ethereum !== 'undefined') {
provider = window.ethereum;
try {
await provider.request({ method: 'eth_requestAccounts' })
} catch (error) {
throw new Error("User Rejected");
}
} else if (window.web3) {
provider = window.web3.currentProvider;
} else if (window.celo) {
provider = window.celo;
} else {
throw new Error("No Web3 Provider found");
}
return provider;
};
export default ConnectToInjected;
Usage:
const provider = await ConnectToInjected();
// Open metamask
await provider.request({ method: 'eth_requestAccounts' });
const web3 = new Web3(provider)

Related

Metamask RPC methods do not exist

I have been following a tutorial on how to create my first Web3js application with solidity and react.
The tutorial was going great until I fall into this problem with metamask RPC.
The tutorial I have been following is this: https://www.youtube.com/watch?v=Wn_Kb3MR_cU&t=6333s&ab_channel=JavaScriptMastery
Right now I'm getting the following errors when trying to run function from ethereum:
inpage.js:1 MetaMask - RPC Error: The method "accounts " does not exist / is not available.
inpage.js:1 MetaMask - RPC Error: The method "eth_accounts " does not exist / is not available.
uncaught (in promise) {code: -32601, message: 'The method "eth_accounts " does not exist / is not available.', data: {…}, stack: '{\n "code": -32601,\n "message": "The method \\"eth…beogaeaoehlefnkodbefgpgknn/common-0.js:18:167275)'}
uncaught (in promise) {code: -32601, message: 'The method "eth_requestAccounts " does not exist / is not available.', data: {…}, stack: '{\n "code": -32601,\n "message": "The method \\"eth…beogaeaoehlefnkodbefgpgknn/common-0.js:18:167275)'}
The file that runs this is a context file TransactionContext.tsx:
import React, { useEffect, useState } from 'react';
import { ethers } from 'ethers';
import { contractABI, contractAddress } from '../utils/constants';
export const TransactionContext = React.createContext({} as any);
const { ethereum } = window as any;
const getEthereumContract = () => {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const transactionsContract = new ethers.Contract(contractAddress, contractABI, signer);
console.log({
provider,
signer,
transactionsContract
})
}
export const TransactionProvider = ({ children }: any) => {
const [currentAccount, setCurrentAccount] = useState('');
const checkIfWalletIsConnected = async () => {
if (!ethereum) return alert("Please install metamask!");
const accounts = await ethereum.request({ method: 'eth_accounts '});
console.log(accounts);
}
const connectWallet = async () => {
try {
if (!ethereum) return alert("Please install metamask!");
const accounts = await ethereum.request({ method: 'eth_requestAccounts '});
setCurrentAccount(accounts[0]);
} catch (e) {
console.log(e);
throw new Error('No Ethereum object.')
}
}
useEffect(() => {
checkIfWalletIsConnected();
}, [])
return (
<TransactionContext.Provider value={{ connectWallet }}>
{children}
</TransactionContext.Provider>
)
}
I see 3 issues in your contract:
1- you are not returning the contract from getEthereumContract. it should be
const getEthereumContract = () => {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const transactionsContract = new ethers.Contract(contractAddress, contractABI, signer);
return transactionsContract
}
I dont see you are using here yet but you might get bug in the future:
2- Error says 'The method "eth_accounts " does not exist ... you have extra space here "eth_accounts ". should be
const accounts = await ethereum.request({ method: 'eth_accounts'});
3- this is similar to second. You have extra space
const accounts = await ethereum.request({ method: 'eth_requestAccounts'});
You need to specify for which account you want to get the signer provider.getSigner(account)
On click of your connect button add this
async function connectWalletHandler() {
if (!ethereum) {
console.log("Make sure you have Metamask installed");
return;
} else {
console.log("Wallet exist");
}
const accounts = await ethereum.request({ method: "eth_requestAccounts" });
if (accounts.length !== 0) {
} else {
console.log("No authorized account found");
}
And put this code in you app.js
const [user,setUser]=useState(null);
Useeffect(()=>{
if (window.ethereum) {
const isMetaMaskConnected = async () => {
let provider = new ethers.providers.Web3Provider(window.ethereum);
const accounts = await provider.listAccounts();
let account = null;
if (accounts.length > 0) {
account = accounts[0];
}
let signer = provider.getSigner(account);
setUser({ provider: provider, signer: signer, account: account });
};
isMetaMaskConnected();
window.ethereum.on("chainChanged", (chainId) => {
window.location.reload();
});
window.ethereum.on("accountsChanged", () => {
window.location.reload();
});
} else {
}},[])
From now you have 3 option first user is null metamask not installed
2 user.account="" or null metamask installed and connected but locket
3 user.account have value this is when the wallet connected to the website and every thing 👍

How do I get the data from an API call, in a different file, in React

I'm trying to update some code, taking into account new sdk versions. I have the new api call in one file:
import { CognitoIdentityProviderClient, ListUsersCommand } from "#aws-sdk/client-cognito-identity-provider";
import awsmobile from "../../aws-exports";
import { Auth } from "aws-amplify";
export default async function ListUsers() {
await Auth.currentCredentials().then((data) => {
const client = new CognitoIdentityProviderClient({
region: awsmobile.aws_project_region,
credentials: data
});
const params = {
UserPoolId: awsmobile.aws_user_pools_id
};
const command = new ListUsersCommand(params);
client.send(command).then(
(data) => {
return data
},
(error) => {
console.log(error)
}
);
});
}
I'm trying to retrive the data in another file:
import ListUsers from "../../../API/cognito/ListUsers";
import ListUsersInGroup from "../../../API/cognito/ListUsersInGroup";
import { useState, useEffect, useRef } from "react";
import PortalUsersTable from "../../../components/tables/PortalUsersTable";
export default function ManageUsers() {
const [userDetails, setUserDetails] = useState("");
const refUsers = useRef();
const refUsersExec = useRef();
const refUsersAdmin = useRef();
const refUsersGroups = useRef();
useEffect(() => {
function getUsers() {
refUsers.current = ListUsers();
refUsersExec.current = ListUsersInGroup("usersAdmin");
refUsersAdmin.current = ListUsersInGroup("usersExec");
//setUsersTloOfficers(apiTloOfficers);
refUsersGroups.current = ListUsersInGroup("usersGroups");
let userData = [];
let arrUsersExec = [];
for (let a in refUsersExec.current.Users) {
arrUsersExec.push(refUsersExec.current.Users[a].Username);
}
let arrUsersAdmin = [];
for (let b in refUsersAdmin.current.Users) {
arrUsersAdmin.push(refUsersAdmin.current.Users[b].Username);
}
let arrUsersGroups = [];
for (let b in refUsersNtigGroups.current.Users) {
arrUsersGroups.push(refUsersGroups.current.Users[b].Username);
}
for (let i in refUsers.current.Users) {
let email = null;
for (let x in refUsers.current.Users[i].Attributes) {
if (refUsers.current.Users[i].Attributes[x].Name === "email") {
email = refUsers.current.Users[i].Attributes[x].Value;
break;
}
}
let memberExec = arrUsersExec.includes(refUsers.current.Users[i].Username);
let memberAdmin = arrUsersAdmin.includes(refUsers.current.Users[i].Username);
let memberGroups = arrUsersGroups.includes(refUsers.current.Users[i].Username);
userData.push({
id: i,
Username: refUsers.current.Users[i].Username,
AccountStatus: refUsers.current.Users[i].UserStatus,
Email: email,
Users: memberGroups,
Exec: memberExec,
Admin: memberAdmin,
});
}
setUserDetails(userData);
}
getUsers();
}, []);
return (
<>
<h2>Manage Portal Users</h2>
<PortalUsersTable userDetails={userDetails} />
</>
);
}
The logic to handle the API data is sound.
This is the old API call:
import AWS from "aws-sdk";
import awsmobile from "../../aws-exports";
import { Auth } from "aws-amplify";
export default async function ListUsers() {
let idToken = "";
await Auth.currentAuthenticatedUser().then((user) => {
idToken = user.signInUserSession.idToken.getJwtToken();
});
AWS.config.region = awsmobile.aws_cognito_region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: awsmobile.aws_cognito_identity_pool_id,
RoleArn: "arn:aws:iam::xxxxxxxxx:role/xxxxxxxxxxxxx",
Logins: { "xxxxxxxxxxxxxxxxxxxx": idToken }
});
let cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
let params = {
UserPoolId: awsmobile.aws_user_pools_id,
AttributesToGet: ["email"]
};
return new Promise((resolve, reject) => {
cognitoidentityserviceprovider.listUsers(params, function (err, result) {
if (err) {
console.log(err);
//onError(err);
reject(err);
return;
}
if (result) {
resolve(result);
}
});
});
}
I can see the new API call is returning the correct data in the console. I think I'm not passing the data between files correctly.
I've tried various ways of changing the API call function, reading the cognito sdk description but it's not the API call that is incorrect.
How can I use the API call data in the separate file?
Even if your API call if correct, it looks like you are not returning anything from your function ListUsers. You are mixing async/await pattern with the then. I assume you have added a console.log right before the return data. Refactoring your function using async/await would look like this :
export default async function ListUsers() {
try {
const data = await Auth.currentCredentials();
const client = new CognitoIdentityProviderClient({
region: awsmobile.aws_project_region,
credentials: data,
});
const params = {
UserPoolId: awsmobile.aws_user_pools_id,
};
const command = new ListUsersCommand(params);
const commandData = await client.send(command);
return commandData;
} catch (error) {
console.log(error);
}
}

Accessing AsyncStorage react native

Hello I am new to react Native and I would like to access the user id that was stored to react Native storage so that I can pass it to the WebSocket connection but it is not returning the id
here is my sample code
import { io } from "socket.io-client/dist/socket.io";
import env from "../utils/env";
import AsyncStorage from "#react-native-async-storage/async-storage";
const getUserData = async () => {
try {
const value = await AsyncStorage.getItem("UserData");
if (value !== null) {
return value;
}
} catch (e) {
// remove error
}
console.log("Done.");
};
getUserData().then((res) => {
let response = JSON.parse(res);
console.log(response._id);
});
let socket = io(`${env.DEV_SERVER_URL}`, {
transports: ["websocket"],
query: `mobileId=${getUserData().then((res) =>{
let response = JSON.parse(res);
return response._id
})}`,
});
export default socket;
const storeData = async (data) =>{
await AsyncStorage.setItem('UserData', JSON.stringify(UserData));
}
try this to store your data you might have forgot to stringify your object before storing it

AWS Cognito not working for IE 11, but works for every other browser

So I am trying to use cognito to manage authentication in my react application, with the identity provider being SAML. This is working very smoothly in Chrome and Firefox, but not in IE 11. Here is I set up my Auth:
import { Component } from 'react';
import { connect } from 'react-redux';
import { CognitoAuth } from 'amazon-cognito-auth-js';
import { signIn, signOutSuccess } from '../store/auth';
import { setupAxios } from '../axios';
import {
AWS_COGNITO_CLIENT_ID,
AWS_COGNITO_USER_POOL_ID,
AWS_COGNITO_REDIRECT_SIGN_IN,
AWS_COGNITO_REDIRECT_SIGN_OUT,
AWS_COGNITO_APP_WEB_DOMAIN
} from '../env';
const cognitoSetup = props => {
//as per documentation
const authData = {
ClientId: AWS_COGNITO_CLIENT_ID,
TokenScopesArray: ['email', 'openid', 'profile'],
RedirectUriSignIn: AWS_COGNITO_REDIRECT_SIGN_IN,
RedirectUriSignOut: AWS_COGNITO_REDIRECT_SIGN_OUT,
AppWebDomain: AWS_COGNITO_APP_WEB_DOMAIN,
IdentityProvider: 'SAML',
UserPoolId: AWS_COGNITO_USER_POOL_ID
};
const auth = new CognitoAuth(authData);
auth.useCodeGrantFlow(); //getting the refresh token
auth.userhandler = {
onSuccess: result => {
const { profile, name, family_name, email } = result.idToken.payload;
//passes role to store for use in the rest of the app
const username = result.idToken.payload.identities[0].userId;
const fullName = `${name} ${family_name}`;
props.signIn({ username, profile, fullName, email });
},
onFailure: function(err) {
console.error(err);
throw err;
}
};
return auth;
};
export class AuthService extends Component {
constructor(props) {
super(props);
this.authService = cognitoSetup(this.props);
//passes the auth to axios to check for token on request
setupAxios(this.authService);
}
componentDidMount() {
const curUrl = window.location.href;
if (curUrl.includes('?code=')) {
this.authService.parseCognitoWebResponse(curUrl);
} else if (!curUrl.includes('?error')) {
this.authService.getSession();
}
}
signOut = async () => {
await this.authService.signOut();
};
async componentDidUpdate(prevProps) {
if (prevProps.shouldSignOut !== this.props.shouldSignOut) {
if (this.props.shouldSignOut) {
await this.signOut();
this.props.signOutSuccess();
}
}
}
//render nothing
render() {
return null;
}
}
const mapState = state => ({
username: state.auth.username,
signedIn: state.auth.signedIn,
shouldSignOut: state.auth.shouldSignOut
});
const mapDispatch = dispatch => ({
signIn: (username, profile) => dispatch(signIn(username, profile)),
signOutSuccess: () => dispatch(signOutSuccess())
});
export default connect(mapState, mapDispatch)(AuthService);
This AuthService.js is rendered upon loading the application. However When loading in IE11, there is an error var jsonDataObject = JSON.parse(jsonData); invalid character.
I have no idea why this is happening. I have investigated and came to the conclusion that this is going on within the package amazon-cognito-auth-js. I'm under the impression this package was made by amazon so I believe the package is not at fault, but I cannot see anyone else with this issue. Does anyone have any suggestions?
EDIT: I do have a polyfill
I saw you used arrow functions => in your code which is not supported by IE. You could use babel to compile it and any other ES6 syntax to ES5. For example, compile:
const cognitoSetup = props => {
...
}
to:
var cognitoSetup = function cognitoSetup(props) {
...
}
Besides, have you imported react-app-polyfill at the first line in your src/index.js? This is required for react app to run in IE 11. You could refer to the answer in this thread for detailed steps.

I am returning an array in the contract. How do I accept it in my app.js using react and web3?

I have used truffle unbox react.
My contract -
pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;
contract SimpleStorage {
enum Status { NotExist, Created, InTransit, PaidFor, Completed }
struct Property {
Status state;
uint price;
address currOwner;
}
Property[] public arr;
mapping (address => uint) balances;
Property public house;
function registerProperty(uint _price) public {
house = Property(Status.Created, _price,msg.sender);
//emit NewProperty(msg.sender, _price);
}
function get()public returns(Status, uint , address){
return (house.state, house. price , house.currOwner);
}
}
App.js -
import React, { Component } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json";
import getWeb3 from "./utils/getWeb3";
import "./App.css";
class App extends Component {
state = { storageValue:"", web3: null, accounts: null, contract: null,newValue:"" };
componentDidMount = async () => {
try {
this.handleChange=this.handleChange.bind(this);
this.handleSubmit=this.handleSubmit.bind(this);
// Get network provider and web3 instance.
const web3 = await getWeb3();
// Use web3 to get the user's accounts.
const accounts = await web3.eth.getAccounts();
// Get the contract instance.
const networkId = await web3.eth.net.getId();
const deployedNetwork = SimpleStorageContract.networks[networkId];
const instance = new web3.eth.Contract(
SimpleStorageContract.abi,
deployedNetwork && deployedNetwork.address,
);
// Set web3, accounts, and contract to the state, and then proceed with an
// example of interacting with the contract's methods.
this.setState({ web3, accounts, contract: instance }, this.runExample);
} catch (error) {
// Catch any errors for any of the above operations.
alert(
`Failed to load web3, accounts, or contract. Check console for details.`,
);
console.error(error);
}
};
handleChange(event){
this.setState({newValue: event.target.value });
}
handleSubmit = async event => {
const { contract, accounts } = this.state;
event.preventDefault();
await contract.methods.registerProperty(this.state.newValue).send({ from: accounts[0] });
const {response} = await contract.methods.get().call();
this.setState({ storageValue: response });
}
runExample = async () => {
const {contract } = this.state;
// Stores a given value, 5 by default.
// await contract.methods.set("").send({ from: accounts[0] });
// Get the value from the contract to prove it worked.
const response = await contract.methods.get().call();
// Update state with the result.
this.setState({ storageValue: response});
};
render() {
//if (!this.state.web3) {
// return <div>Loading Web3, accounts, and contract...</div>;
//}
return (
<div className="App">
<h1>Good to Go!</h1>
<div>The stored value is: {this.state.storageValue}</div>
<form onSubmit={this.handleSubmit}>
<input type="text" value ={this.state.newValue} onChange={this.handleChange.bind(this)} />
<input type="submit" value="submit" />
</form>
</div>
);
}
}
export default App;
If I return only a single value the code runs perfectly but for an array if I have to use a map, how do I do it? It is an array of structs, I have don't return as an array also, and return multiple values still it shows an error.

Resources