Detect MetaMask logout (Ethereum) - ethereum

I've looked at the documentation here https://metamask.github.io/metamask-docs/Main_Concepts/Getting_Started
But I'm not sure how to detect a user logging out of MetaMask?

window.ethereum.on('accountsChanged', (accounts) => {
// If user has locked/logout from MetaMask, this resets the accounts array to empty
if (!accounts.length) {
// logic to handle what happens once MetaMask is locked
}
});
Thus, using the above you can detect lock/logout of MetaMask.

window.ethereum.on('accountsChanged', function (accounts) {
let acc = accounts[0]
acc will be undefined if they logged out.

From MetaMask Ethereum Provider API:
ethereum.on('accountsChanged', handler: (accounts: Array<string>) => void);
The MetaMask provider emits this event whenever the return value of the eth_accounts RPC method changes. eth_accounts returns an array that is either empty or contains a single account address. The returned address, if any, is the address of the most recently used account that the caller is permitted to access. Callers are identified by their URL origin, which means that all sites with the same origin share the same permissions.

Metamask documentation suggest you to refresh the page if account is changed.
const setAccountListener = (provider) => {
provider.on("accountsChanged", (_) => window.location.reload());
provider.on("chainChanged", (_) => window.location.reload());
};
Then call this in useEffect
useEffect(() => {
// Load provider
if (provider) {
....
setAccountListener(provider);
// add more logic
} else {
console.error("Please, install Metamask.");
}
};
}, []);
New Feature: _metamask.isUnlocked()
Metamask adds _metamask.isUnlocked() experimental property on ethereum.
const reload = () => window.location.reload();
const handleAccount = (ethereum) => async () => {
const isLocked = !(await ethereum._metamask.isUnlocked());
if (isLocked) {
reload();
}
};
const setListener = (ethereum) => {
ethereum.on("chainChanged", reload);
ethereum.on("accountsChanged", handleAccount(ethereum));
};
const removeListener = (ethereum) => {
ethereum.removeListener("chainChanged", reload);
ethereum.removeListener("accountsChanged", handleAccount(ethereum));
};

Related

How to pass updated set State value in axios request as params

I'm a beginner in react native, I'm trying to get user information from mysql database through an axios API get request.
Once logged in, I stored email address in AsyncStorage and later want to use that email address from AsyncStorage as params or parameters to get the user details.
I wrote a code which set initial state of the setState as 'na'. Please help me how I can pass the email address from AsyncStorage as params or parameters.
Here is my code.
// to load email address
const [SessionEmail, setSessionEmail] = useState('na');
// to load users info
const [users, setUsers] = useState([]);
useFocusEffect(
React.useCallback(() => {
getUsername();
getUsersInfoFromAPI();
}, [])
);
// to get the session username from localstorage
const getUsername = async () => {
try {
const username = await AsyncStorage.getItem('Username')
if (username !== null) {
setSessionEmail(username);
}
} catch (e) {
console.log(e);
}
}
// API Calling user details
const getUsersInfoFromAPI = async () => {
await axios.get(`https://myapi.co.in/api/user/?email=${SessionEmail}`)
.then(response => {
setUser(response.data);
})
.catch(error => {
console.log(error);
});
}
After the page is rendered, and I load page from metro, I can see the parameters have been sent to server.
Update your code in this way:
useFocusEffect(
React.useCallback(() => {
getUsername();
}, [])
);
Instead of saving your email to state, sent it to function directly but if you are using it for other reason you can still save it but call function while getting username from AsyncStorage with username parameter like below.
// to get the session username from localstorage
const getUsername = async () => {
try {
const username = await AsyncStorage.getItem('Username')
if (username !== null) {
getUsersInfoFromAPI(username);
}
} catch (e) {
console.log(e);
}
}
// API Calling user details
const getUsersInfoFromAPI = async (email) => {
await axios.get(`https://myapi.co.in/api/user/?email=${email}`)
.then(response => {
setUser(response.data);
})
.catch(error => {
console.log(error);
});
}
const [users, setUsers] = useState([]);
here you can use like this
const [users, setUsers] = useState();
hope this will help you

window.ethereum.providers.find((provider) => provider.isMetamask) returns undefined

Metamask wallet will not connect if coinbase is installed.
This is a critical issue for anyone trying to execute transactions on their website. Asking users to remove their coinbase wallet is a major deterrent.
https://docs.metamask.io/guide/ethereum-provider.html#using-the-provider
This link on the metamask website demonstrates that they are aware of the issue but no documentation addresses it.
This issue can't be impossible to fix because sites like uniswap allow users to select which wallet. So I think it would be helpful if this was addressed in the documentation.
This is the original code for the connect function:
const connectMetamask = async (setAlert, setCurrentAccount) => {
const {ethereum} = window
const isInstalled = () => {
return Boolean(ethereum && ethereum.isMetaMask)
}
const isConnect = () => {
return Boolean(ethereum && ethereum.isConnected()) // <-- this is the issue
}
try {
if (!isInstalled()) {
console.log("No metamask!"); // <-- this works
setAlert(true);
return;
}
if (!isConnect()) {
console.log("Metamask not connected!");
setAlert(true)
return;
}
const chainId = await ethereum.request({ method: "eth_chainId" });
} catch (error) {
console.error(error);
}
};
This code works fine to connect metamask IF coinbase wallet is not installed.
https://docs.cloud.coinbase.com/wallet-sdk/docs/injected-provider-guidance
This link suggests what to do- but it doesn't work (for me at least).
if (window.ethereum.providers?.length) {
window.ethereum.providers.forEach(async (p) => {
if (p.isMetaMask) provider = p;
});
}
window.ethereum.providers returns an array, first element being the coinbase wallet which works fine, second being a proxy containing metamask. The properties of this proxy object are not accessible.
As per some answers I've written (which is the same as the code in the coinbase example):
const metamaskProvider = await window.ethereum.providers.find((provider) => provider.isMetaMask);
ethereum.setSelectedProvider(metamaskProvider)
Logging metamaskProvider returns undefined.
Logging window.ethereum.providers returns an array:
0: v {_events: {…}, _eventsCount: 0, _maxListeners: undefined, _filterPolyfill: e.FilterPolyfill, _subscriptionManager: e.SubscriptionManager, …}
1: Proxy {_events: {…}, _eventsCount: 0, _maxListeners: 100, _log: u, _state: {…}, …}
1 is the metamask provider.
It contains the following properties:
1: Proxy
[[Handler]]: Object
deleteProperty: ()=>!0
[[Prototype]]: Object
[[Target]]: l
chainId: "0x1"
enable: ƒ ()
isMetaMask: true
....
metamaskProvider returns undefined. How do I access the isMetaMask property, and set my selectedProvider to metamaskProvider ?
I think if you have multiple wallets are installed, you would not have window.ethereum.providers defined. I currently have only "metamask", I get this error: Uncaught TypeError: Cannot read properties of undefined (reading 'find')
if (typeof window.ethereum !== "undefined") {
let provider = window.ethereum;
// if multiple wallet are installed
if (window.ethereum.providers?.length) {
window.ethereum.providers.find(async (p) => {
if (p.isMetaMask) provider = p;
});
}
also you should not destructure
const metamaskProvider = await window.ethereum.providers.find((provider) => provider.isMetaMask);
My fix:
const connectMetamask = async (setAlert, setCurrentAccount) => {
let { ethereum } = window;
try {
if (!ethereum) {
console.log("No metamask!");
setAlert(true);
return;
}
try{
if (!ethereum.isConnected()) {
console.log("Metamask not connected!");
setAlert(true)
return;
}
} catch (error){
console.error(error)
try{
console.log('providers',window.ethereum.providers);
console.log('ethVar',ethereum)
ethereum = await window.ethereum.providers.find(
(provider) => provider.isMetaMask );
console.log('ethVarAfterFind', ethereum)
} catch (error){
console.error(error)
}
}
const chainId = await ethereum.request({ method: "eth_chainId" });
.....
} catch (error) {
console.log('error:')
console.error(error);
}
};
export default connectMetamask;
Basically just :
ethereum = await window.ethereum.providers.find( (provider) => provider.isMetaMask );
const chainId = await ethereum.request({ method: "eth_chainId" });
Honestly I'm pretty sure I tried this already and it wasn't working but, it is now ¯\_(ツ)_/¯

#MetaMask / detect-provider: handle Accounts Changed

I'm using #metamask/detect-provider, when I use the second account of my Metamask wallet, this second account is not considered in execution. This is the first account that is always taken in the execution
accountsChanged event will be emitted whenever you change the account. So write a callback to handle it. I just set it in a state.
useEffect(() => {
// you have to write code to detect provider. [#metamask/detect-provider][1]
// if there is no accound set it null as default
provider &&
provider.on("accountsChanged", () => setAccount(accounts[0] ?? null));
}, []);
or
useEffect(() => {
window.ethereum &&
window.ethereum.on("accountsChanged", () => setAccount(accounts[0] ?? null));
}, []);

trying to deploy a todolist in blockchain, when adding a new task, I get invalid address error

I am making a simple block chain using the ETH blockchain technology.
I am making a simple todolist, following this tutorial.
My todolist works fine and I can see the tasks in there, how ever when I try to add a new task I get this following error:
Uncaught (in promise) Error: invalid address
v http://localhost:3000/js/web3.min.js:2
l http://localhost:3000/js/web3.min.js:2
formatInput http://localhost:3000/js/web3.min.js:2
formatInput http://localhost:3000/js/web3.min.js:2
toPayload http://localhost:3000/js/web3.min.js:2
e http://localhost:3000/js/web3.min.js:2
sendTransaction http://localhost:3000/js/web3.min.js:2
execute http://localhost:3000/js/web3.min.js:2
synchronizeFunction http://localhost:3000/vendor/truffle-contract/dist/truffle-contract.js:206
synchronizeFunction http://localhost:3000/vendor/truffle-contract/dist/truffle-contract.js:157
promise callback*synchronizeFunction/< http://localhost:3000/vendor/truffle-contract/dist/truffle-contract.js:156
createTask http://localhost:3000/js/app.js:124
onsubmit http://localhost:3000/:1
web3.min.js:2:4288
createTask http://localhost:3000/js/app.js:125
AsyncFunctionThrow self-hosted:696
(Async: async)
onsubmit http://localhost:3000/:1
​
this is my app.js
App = {
loading: false,
contracts: {},
load: async () => {
await App.loadWeb3();
await App.loadAccount();
await App.loadContract();
await App.render();
},
// https://medium.com/metamask/https-medium-com-metamask-breaking-change-injecting-web3-7722797916a8
loadWeb3: async () => {
if (typeof web3 !== "undefined") {
App.web3Provider = web3.currentProvider;
web3 = new Web3(web3.currentProvider);
} else {
window.alert("Please connect to Metamask.");
}
// Modern dapp browsers...
if (window.ethereum) {
window.web3 = new Web3(ethereum);
try {
// Request account access if needed
await ethereum.enable();
// Acccounts now exposed
web3.eth.sendTransaction({
/* ... */
});
} catch (error) {
// User denied account access...
}
}
// Legacy dapp browsers...
else if (window.web3) {
App.web3Provider = web3.currentProvider;
window.web3 = new Web3(web3.currentProvider);
// Acccounts always exposed
web3.eth.sendTransaction({
/* ... */
});
}
// Non-dapp browsers...
else {
console.log(
"Non-Ethereum browser detected. You should consider trying MetaMask!"
);
}
},
loadAccount: async () => {
// Set the current blockchain account
App.account = web3.eth.accounts[0];
console.log(App.account);
},
loadContract: async () => {
// Create a JavaScript version of the smart contract
const todoList = await $.getJSON("TodoList.json");
App.contracts.TodoList = TruffleContract(todoList);
App.contracts.TodoList.setProvider(App.web3Provider);
// Hydrate the smart contract with values from the blockchain
App.todoList = await App.contracts.TodoList.deployed();
// console.log(todoList);
},
render: async () => {
// Prevent double render
if (App.loading) {
return;
}
// Update app loading state
App.setLoading(true);
// Render Account
$("#account").html(App.account);
// Render Tasks
await App.renderTasks();
// Update loading state
App.setLoading(false);
},
renderTasks: async () => {
// Load the total task count from the blockchain
const taskCount = await App.todoList.taskCount();
const $taskTemplate = $(".taskTemplate");
// Render out each task with a new task template
for (var i = 1; i <= taskCount; i++) {
// Fetch the task data from the blockchain
const task = await App.todoList.tasks(i);
const taskId = task[0].toNumber();
const taskContent = task[1];
const taskCompleted = task[2];
// Create the html for the task
const $newTaskTemplate = $taskTemplate.clone();
$newTaskTemplate.find(".content").html(taskContent);
$newTaskTemplate
.find("input")
.prop("name", taskId)
.prop("checked", taskCompleted)
.on("click", App.toggleCompleted);
// Put the task in the correct list
if (taskCompleted) {
$("#completedTaskList").append($newTaskTemplate);
} else {
$("#taskList").append($newTaskTemplate);
}
// Show the task
$newTaskTemplate.show();
}
},
createTask: async () => {
App.setLoading(true);
const content = $("#newTask").val();
await App.todoList.createTask(content);
window.location.reload();
},
toggleCompleted: async (e) => {
App.setLoading(true);
const taskId = e.target.name;
await App.todoList.toggleCompleted(taskId);
window.location.reload();
},
setLoading: (boolean) => {
App.loading = boolean;
const loader = $("#loader");
const content = $("#content");
if (boolean) {
loader.show();
content.hide();
} else {
loader.hide();
content.show();
}
},
};
$(() => {
$(window).load(() => {
App.load();
});
});
in console it shows me the address to be: 0xc5cfa0a0345f74e26cecfd8ec3a5cfa3843955ac
I am using metamask and genache, and I tried to connect my smart contacrt to my memask wallet, so I know I am connected, but I am not sure why I am getting this error.
SHould I delete my metamask and do it again?
I tried to look for this solution here, but the solutions are mostly old and doesnt make scnese what I need to do.
Help would be really apperciated.
fixed it, in the app.js you have to replace this line:
App.account = web3.eth.accounts[0];
with the following line:
web3.eth.defaultAccount=web3.eth.accounts[0]

From ES2018 async/await to ES2015 Promises . ... timeout

I am trying to convert an ES2018 async function into an ES2015 (ES6) function, but I get a timeout, guess my ES2015 version is wrong...but where?
ES2018 version
async function connectGoogleAPI () {
// Create a new JWT client using the key file downloaded from the Google Developer Console
const client = await google.auth.getClient({
keyFile: path.join(__dirname, 'service-key.json'),
scopes: 'https://www.googleapis.com/auth/drive.readonly'
});
// Obtain a new drive client, making sure you pass along the auth client
const drive = google.drive({
version: 'v2',
auth: client
});
// Make an authorized request to list Drive files.
const res = await drive.files.list();
console.log(res.data);
return res.data;
}
ES2015 version w/Promise
function connectGoogleAPI () {
return new Promise((resolve, reject) => {
const authClient = google.auth.getClient({
keyFile: path.join(__dirname, 'service-key.json'),
scopes: 'https://www.googleapis.com/auth/drive.readonly'
});
google.drive({
version: 'v2',
auth: authClient
}), (err, response) => {
if(err) {
reject(err);
} else {
resolve(response);
}
}
});
}
You haven't translated the await of getClient. Remember, await = then (roughly). You're also falling prey to the promise creation anti-pattern: When you already have a promise (from getClient), you almost never need to use new Promise. Just use then.
Here's an example with each await converted into a then, using the chain for the subsequent operations:
function connectGoogleAPI () {
// Create a new JWT client using the key file downloaded from the Google Developer Console
return google.auth.getClient({
keyFile: path.join(__dirname, 'service-key.json'),
scopes: 'https://www.googleapis.com/auth/drive.readonly'
}).then(client => {
// Obtain a new drive client, making sure you pass along the auth client
const drive = google.drive({
version: 'v2',
auth: client
});
// Make an authorized request to list Drive files.
return drive.files.list();
}).then(res => {
console.log(res.data);
return res.data;
});
}
That last part can be just
}).then(res => res.data);
...if you remove the console.log. (Or we could abuse the comma operator.)
Notes:
Each await needs to become a then handler (there were two in the original, awaiting getClient and drive.files.list)
In a then handler, if you have to wait for another promise (such as the one from drive.files.list) you typically return it from the handler, and then use another handler to handle that result (which is why I have return drive.files.list() and then a separate handler for converting res to res.data)
Re that second point: Sometimes nesting is appropriate, such as when you need to combine the result with some intermediate value you only have with in your then handler. (For instance, if we wanted to combine res.data with client.) But generally, prefer not to nest.