Update

On November 2nd MetaMask and other dapp browsers will stop exposing user accounts by default. This will make some code from this paper to break. I will publish updated version with web3 1.0 and new MetaMask interface.


Metamask is de facto standard for dApps on the web. It injects web3 instance into window object making it available for js code.

We are going to use web3 0.20 version, not web3 1.0. Code for web3 1.0 would be different.

Every dApp has its mission, but the way they interact with metamask is common. In this paper, we would cover the ten most common practices to handle web3/metamask interactions.

#1. Detect Metamask and Instantiate Web3

According to docs the best way to do it.

What is going on here?
First, we check if web3 injected. If it is injected we create a new instance using the injected provider. Why is that? Because we want to use our library version, not the one injected by metamask.
If web3 is not present, we try to connect to localhost provider, like ganache.

#2. Check If Metamask Locked

Metamask can be installed but locked. In order to interact with a user account and send transactions the user has to unlock metamask.

#3. Check Current Network

There are many test networks beyond the main network. Typically you contract is deployed to a certain network and you want to be sure user run metamask on the same network.

#4. Get Current Account

A user may have multiple accounts at Metamask, but he expects dApp to interact with the current one.

You always should grab the account from web3 instance and do not keep and reuse it, because the user may change account at any time.

#5. Get Balance on Current Account

Here we use function getAccount from #4 and call getBalance. Easy.

#6. Detect Current Account Changed.

A user may change his account at any time. You dApp should be ready for that and react properly.

#7. Detect Metamask Locked/Unlocked

Similar to #6. A user may locked/unlock anytime. dApp should handle it right.

#8. Handle Cancel/Confirm

Once a user interacts with your dApp you have to send transaction using web3 API. A user may press cancel or confirm button on metamask popup, which may lead to UI inconsistency if not handled correctly.
In order to return instantly with transaction hash, call contract.methodName.sendTransaction.

#9. Get Transaction Receipt

Once your dApp transaction is mined, transaction receipt becomes available. Yet there is no event/notification, so we have to implement a poll mechanism.

#10. Listen for Web3 Events

Solidity events are great. They allow switching from ugly polling to just push mechanism. Assuming you contract implements all necessary events. You can completely avoid polling and just react to events. Event callback returns a lot of data, but we are mostly interested in args.

Summary

Whatever your dApp is about it still has to do common tasks, such as detect web3, account state, and balance, current network, handle transactions and events. We showed how it can be done using ten code snippets.

PS

A lot of examples here use methods which might throw an error because of metamask state or some variables being undefined at the moment of a call. You should wrap them in try/catch in a production environment.
Async/await used here for simplicity. Can be replaced with Promise then/catch.