Links

dApp Integration with MetaMask Flask

Introduction

For this tutorial, we'll be building a basic dApp using an HTML/CSS/JS base. This dApp onboards users to MetaMask Flask, installs the quai-snap npm package, and generates a set of 13 addresses, one for each shard. To get started, you can clone our Project Files from GitHub or use your own clean directory.
Because of the limited scope of a traditional javascript backend, the dApp offers no additional functionalities. It is highly recommended that developers utilize a more advanced framework such as React.

MetaMask Flask & Snaps

MetaMask Flask

MetaMask Flask is an experimental development environment that allows developers to create and test custom MetaMask APIs and other features. MetaMask Flask operates almost identically to vanilla MetaMask, but with the option to integrate custom Snaps.

Snaps

Snaps are unique JavaScript programs that allow code to run inside a secure execution environment within the MetaMask Flask browser extension. Snaps bring MetaMask functionality to blockchain networks that have yet to be integrated into the official MetaMask browser extension. The official Snaps development guide with full documentation can be found here. Snaps are currently distributed as npm packages.

Quai-Snap

As mentioned earlier, Snaps allow developing chains to integrate into the MetaMask ecosystem. Quai-Snap is an npm package that offers Quai Network users a streamlined and familiar experience when using Quai based dApps and managing their funds. A list of available API calls for application developers can be found on the Quai-Snap npm registry page. Utilization of Quai-Snap requires users to disable the traditional MetaMask browser extension temporarily.

Project Setup

Now that you're familiar with MetaMask Flask and Snaps, make sure you've read through our docs and understand the basics of Quai Network's architecture.
To get started, make sure you have:
  1. 1.
    The MetaMask Flask Extension downloaded.
  2. 3.
    Node.js downloaded and installed.
  3. 4.
    Your favorite Text Editor or IDE installed. We recommend using Visual Studio Code.
  4. 5.
    Browserify open source bundler

Open Project Folder

This tutorial follows the app structure outlined in the quai-sample-dapp repo. If you've already cloned the quai-sample-dapp, open a terminal and navigate to its location. Inside the folder, the file structure should look like this:
.
├─ assets
├─ index.html
├─ index.js
├─ css
| └─ styles.css
├─ package-lock.json
├─ package.json
└─ README.md
There are some extra files inside assets that aren't pertinent to this tutorial. No need to worry!

Install Dependencies

To install your basic dependencies, open up your terminal again and run the following:
npm install
Once the installation has finished, you should have a /node-modules folder that houses all of the required dependencies.
For ease of use, I recommend using the VS Code Live Server extension to spin up a server for running and testing your app.

Basic Actions

Navigate to the index.js file, where you'll find the majority of the logic for our app.
It should look like this. Don't worry about lines 1-8, we'll touch on that later.
const initialize = async () => {
// Flask logic goes here
};
window.addEventListener('DOMContentLoaded', initialize);
The function above is called as soon as the DOM content is initialized. Before we get started writing any logic, let's layout the todo list for our app.
  • Connecting to our MetaMask Flask Wallet
  • Display the current connected account
  • Sort accounts by shard ID and display them

Connecting to Flask

The first and most important thing our dApp should do is connect to our Flask Wallet. To do that we need to:
  1. 1.
    Create a function that checks if the MetaMask Flask Chrome extension is installed.
  2. 2.
    If Flask is not installed we should:
    1. 1.
      Change our connectButton to display Click here to install Flask
    2. 2.
      When clicked, that button should take us to the Flask installation page.
    3. 3.
      Disable connectButton functionality
  3. 3.
    If Flask is installed, we should:
    1. 1.
      Change our connectButton to Connect
    2. 2.
      When clicked, that button should connect to our Flask Wallet
    3. 3.
      Disable the connectButton.

1. Flask Extension Check

In our code, we need to access the connectButton from our index.html.
const connectButton = document.getElementById('connect');
const initialize = async () => {
// Flask logic goes here
...
We now need to create a provider check that determines if Flask is installed. For vanilla MetaMask, this is typically done with a simple function, but since we're using Flask, we have to get a little creative. More info can be found here.

Tip

For this part we will be using the '@metamask/detect-provider' library we installed during the npm install. Learn more here.
Inclusion of the detect-provider library can be seen below.
const detectEthereumProvider = require('@metamask/detect-provider');
const connectButton = document.getElementById('connect');
const initialize = async () => {
// returns Ethereum provider as string
const provider = await detectEthereumProvider();
// returns isFlask boolean to indicate whether flask is installed
const isFlask = (await provider?.request({ method: 'web3_clientVersion' }))?.includes('flask');
};
window.addEventListener('DOMContentLoaded', initialize);
The isFlask variable returns a boolean that indicates whether or not flask is installed.
Next, we can use this check to change the content of the connect button based on if the Flask Extension is installed or not.
const detectEthereumProvider = require('@metamask/detect-provider');
const connectButton = document.getElementById('connect');
const initialize = async () => {
// returns Ethereum provider as string
const provider = await detectEthereumProvider();
// returns isFlask boolean to indicate whether flask is installed
const isFlask = (await provider?.request({ method: 'web3_clientVersion' }))?.includes('flask');
const updateConnectButton = () => {
// Now we check to see if Flask is installed
if (!isFlask) {
// If flask isn't installed, ask the user to install it!
connectButton.innerHTML = "Install MetaMask Flask";
} else {
// If flask is installed, ask the user to connect their wallet.
connectButton.innerHTML = "Connect with Flask";
}
}
updateConnectButton();
};
window.addEventListener('DOMContentLoaded', initialize);

2. Flask "Not Installed" Flow

In the code above where Flask is not installed and the connectButton displays Install MetaMask Flask, we must make sure that when the button is clicked we:
  1. 1.
    Display an alert that provides information about MetaMask Flask usage.
  2. 2.
    Direct the user to the Metamask Flask installation page.
const updateConnectButton = () => {
// Now we check to see if Flask is installed
if (!isFlask) {
console.log("Flask is not installed")
// Display flask information alert
$('flask-notinstalled-alert').show();
// If flask isn't installed, ask user to do so. Make button link to install page.
connectButton.innerHTML = '<a href="https://metamask.io/flask/" target="_blank">Install MetaMask Flask</a>';
} else {
// If flask is installed, ask the user to connect their wallet.
connectButton.innerHTML = "Connect with Flask";
}
}
updateConnectButton();
Now if a user of our dApp does not have the MetaMask Flask Extension, they can easily install it! After installing the extension, they can refresh the page and continue connecting to our dApp.

3. Flask "Installed" Flow

Now that we've accounted for our users not having Flask installed, we can handle the case where a user already has the Flask Extension. We'll need to revisit our updateConnectButton block of code to add some extra functionality.
const updateConnectButton = () => {
// Now we check to see if Flask is installed
if (!isFlask) {
console.log("Flask is not installed")
// Display flask information alert
$('flask-notinstalled-alert').show();
// If flask isn't installed, ask user to do so. Make button link to install page.
connectButton.innerHTML = '<a href="https://metamask.io/flask/" target="_blank">Install MetaMask Flask</a>';
} else {
console.log("Flask is installed")
// If flask is installed, ask the user to connect their wallet.
connectButton.innerHTML = "Connect with Flask";
// When the button is clicked, call this function to connect the user's wallet.
connectButton.onclick = onClickConnect;
// Enable clicking of the connect button
connectButton.disabled = false;
}
}
updateConnectButton();
We've now created an onClickConnect function that will be called when the connectButton is clicked. This will prompt the user to connect with their Flask wallet.
In the next section, we'll talk more about what goes inside of the onClickConnect function as well as other Flask RPC API calls you can make inside of your dApp.

App Functionality

To introduce key functionality into our dApp, we must utilize altered Ethereum RPC API calls. Documentation on the wide spectrum of Quai specific API calls can be found here.

Get Quai Network Accounts

Referring back to the onClickConnect function from before, we must connect to the user's Flask wallet and obtain their set of accounts. Let's build the onClickConnect function and the logic that goes along with it.
Above your updateConnectButton function insert this code:
const onClickConnect = async () => {
await ethereum.request({
method: 'wallet_enable',
params: [{wallet_snap: { ['npm:@quainetwork/quai-snap']: {version: '0.1.0-pre.27'} },
}]
});
let newAccounts = await ethereum.request({
method: 'wallet_invokeSnap',
params: ['npm:@quainetwork/quai-snap', {
method: 'getAccounts'
}]
});
}
Make sure quai-snap version in the code above is up to date with the most current release.
The above code will prompt Flask to connect your wallet when you click the connectButton, install the specified version of quai-snap and get the list of addresses associated with your Flask wallet.
We can also introduce a check that verifies the accounts object is not empty, and if it is, creates a new set of addresses, one per shard, for our user. Modify the onClickConnect function like below:
const onClickConnect = async () => {
await ethereum.request({
method: 'wallet_enable',
params: [{wallet_snap: { ['npm:@quainetwork/quai-snap']: {version: '0.1.0-pre.27'} },
}]
});
let accounts = await ethereum.request({
method: 'wallet_invokeSnap',
params: ['npm:@quainetwork/quai-snap', {
method: 'getAccounts'
}]
});
if (accounts.length == 0) {
const response = await ethereum.request({
method: 'wallet_invokeSnap',
params: ['npm:@quainetwork/quai-snap', {
method: 'generateAllAccounts'
}]
})
}
}
You can now access your user's addresses to display, manipulate or print out to the console! Additional account display and sorting functionalities can be found in quai-sample-dapp.

Conclusion

Congratulations! You've completed the setup of a basic Quai Network dApp that onboards users to MetaMask Flask, allows them to connect their wallet, and return all of their addresses.
A number of other functionalities that can be leveraged inside of the HTML/CSS framework can be found in the quai-sample-dapp repo.
Here's a quick overview of the code we've worked on so far.
const detectEthereumProvider = require('@metamask/detect-provider');
const connectButton = document.getElementById('connect');
const initialize = async () => {
// returns Ethereum provider as string
const provider = await detectEthereumProvider();
// returns isFlask boolean to indicate whether flask is installed
const isFlask = (await provider?.request({ method: 'web3_clientVersion' }))?.includes('flask');
// Function called upon clicking connect button
const onClickConnect = async () => {
// enables and installs quai-snap
await ethereum.request({
method: 'wallet_enable',
params: [{wallet_snap: { ['npm:@quainetwork/quai-snap']: {version: '0.1.0-pre.27'} },
}]
});
// gets object newAccounts
let accounts = await ethereum.request({
method: 'wallet_invokeSnap',
params: ['npm:@quainetwork/quai-snap', {
method: 'getAccounts'
}]
});
// generates accounts if newAccounts is an empty object
if (accounts.length == 0) {
const response = await ethereum.request({
method: 'wallet_invokeSnap',
params: ['npm:@quainetwork/quai-snap', {
method: 'generateAllAccounts'
}]
})
}
}
const updateConnectButton = () => {
// Now we check to see if Flask is installed
if (!isFlask) {
console.log("Flask is not installed")
// Display flask information alert
$('flask-notinstalled-alert').show();
// If flask isn't installed, ask user to do so. Make button link to install page.
connectButton.innerHTML = '<a href="https://metamask.io/flask/" target="_blank">Install MetaMask Flask</a>';
} else {
console.log("Flask is installed")
// If flask is installed, ask the user to connect their wallet.
connectButton.innerHTML = "Connect with Flask";
// When the button is clicked, call this function to connect the user's wallet.
connectButton.onclick = onClickConnect;
// Enable clicking of the connect button
connectButton.disabled = false;
}
}
updateConnectButton();
};
window.addEventListener('DOMContentLoaded', initialize);