MetaMask News

Aleph Zero Snap: Onboarding Web3 Users to Aleph Zero Network

A MetaMask Snap that onboards users to the Aleph Zero network

by MetaMaskNovember 7, 2022
aleph zero

Snaps is the roadmap to making MetaMask the most extensible wallet in the world. As a developer, you can bring your features and APIs to MetaMask in totally new ways. Web3 developers are the core of this growth and this series aims to showcase the novel Snaps being built today.

Aleph Zero Snap with Piotr and Michael

We chat with Web3 developers Piotr and Michael who built the Aleph Zero Snap at the ETHWarsaw Hackathon.

Snap Repo: https://github.com/piotr-roslaniec/ethwarsaw-2022/tree/main/azero-snap/packages/snap-adapter

Why did you build it?

In our views the wallet experience in Polkadot ecosystem is still lacking. On the other hand, integrating with MetaMask Snaps could help onboard an existing population of web3 users to Aleph Zero network.

Can you walk us through the technical implementation?

The Adapter

Our snap is composed from two parts. First is a website that users interact with. The second one is the actual Snap. Those two elements work in a way similar to client-server architecture, where the snap is a server and the website is the client. To reflect this natural boundary we have extracted snap API to a separate package named adapter. It exposes all possible methods the website may use to interact with the snap. Such a layer of abstraction, ease usage of the Snap by other clients (websites). Our API can be described by four methods, which names are pretty self-explanatory. If u want to deep dive, here is the full code.

export const getAccountFromSeed = async (seed: string): Promise<PublicAccount> => {
  return await requestSnap("getAccountFromSeed", [seed]);
}

export const generateNewAccount = async (): Promise<PublicAccount> => {
  return await requestSnap("generateNewAccount", []);
}

export const signTransaction = async (transaction: SignerPayloadJSON): Promise<{ signature: string }> => {
  return await requestSnap("signTransaction", [transaction as unknown as string]);
}

export const getAccounts = async (): Promise<string[]> => {
  return await requestSnap("getAccounts", []);
}

The app

So, we explained the glue between our layers, let’s move on to the first layer - website. We have forked the official browser based polkadot app. Then we had to replace core functionalites like signing transactions, creating accounts or recovering them, from browser based wallet to the Snap based. For example to sign transactions inside MetaMask we changed implementation of QrSigner to use Snap signTransaction instead.

Integrating MetaMask Snap with the polkadot app introduced two improvments:

  • Firstly, it allowed to inherit the security model from the MetaMask extension instead of using the polkadot app wallet, which is based on local storage.
  • Secondly, we could use MetaMask seed to create a polkadot app in a deterministic way, so whenever we export our seed, the Aleph Zero key will be also exported.

The MetaMask API

MetaMask provides API calls, which can be used by snaps if permitted by user. In this case we have levarged two of them:

snap_manageState - which allow as two store encrypted data inside MeteMask. For instance we use it to store created accounts. Interacting with state is very easy and can be simplified to:

const request = async (method: 'clear' | 'get' | 'update', newState?: unknown): Promise<any> => {
if (['get', 'clear'].includes(method) && newState) {
    throw new Error(`Cannot ${method} with newState`);
}
if (method == 'update' && !newState) {
    throw new Error('Cannot call update without newState');
}

const params = newState ? [method, newState] : [method];
return await wallet.request({
    method: `snap_manageState`,
    params,
  });
}

snap_getBip44Entropy_ - to get entropy from MetaMask seed. docs

It is worth to mention that there exists very cool MetaMask library passworder, which allow us to easily encrypt and decrypt state. In our case as a symmetric key to encrypt state we have used entropy from the MetaMask seed once again.

The SDK

So let’s recap, we have hacked polkadot app by injecting some javascript here and there enabling usage of the Snap. The Snap can get entropy from the seed inside MetaMask and manage encrypted state. So the last step would be to integrate with the SDK for the Aleph Zero - polkadotjs.

That seems like a easy task, just run npm install @polkadot/api and import it. However importing dependencies to Snap can be challening. This difficulties appears, because Snap is not evaluated directly on javascript virtual machine, it first has to be sandboxed in Secure Ecma Script (SES). In short, code which normally compile without any problems could fail when loading in SES.

Code

When working with most of the librariers we wont have any problems, but it wasn’t case for us. We had two issues with evaluating polkadotjs SDK inside SES, to mitigate them we have written two scripts which were run on bundled source code.

First of them was caused by incorrectly formatted wasm code embedded inside javascript file. Here is the fixing script:

const fs = require('fs').promises;
const path = require('path');

const dirPath =
  '../../../../node_modules/@substrate/smoldot-light/dist/cjs/instance/autogen';

// SES doesn't like double slashes in strings so we need to replace them with single slashes
// and string concatenation.
async function fixDoubleSlash() {
  const files = ['wasm0.js', 'wasm1.js', 'wasm2.js'];
  for (const file of files) {
    console.log('Fixing ${file}');
    const filePath = path.join(__dirname, dirPath, file);
    const contents = await fs.readFile(filePath, 'utf8');
    let fixedContents = contents;
    while (fixedContents.includes('//')) {
      fixedContents = fixedContents.split('//').join('/"+"/');
    }
    await fs.writeFile(filePath, fixedContents);
  }
}

fixDoubleSlash();

Second issue was also caused, by polkadot extension, which was trying to access window.addEventListener method, which is forbidden in SES. It was also trivally solved with simple

const fs = require('fs').promises;
const path = require('path');

const bundlePath = path.join(__dirname, '../dist/bundle.js');

async function fix() {
  console.log(`Fixing ${bundlePath}`);
  const contents = await fs.readFile(bundlePath, 'utf8');
  const replaceMissingTypes = '\n\nwindow.addEventListener = function(){};\n\n';

  // Looking for a first breakline after the first line of the file
  // This is where we can safely inject our code
  const fixed = contents.replace('\n\n', replaceMissingTypes);
  await fs.writeFile(bundlePath, fixed);
}

fix();

What are the next steps if you would implement this?

We’d love to make make our snap fully compatible with polkadot.js and viable as a web wallet in Polkadot ecosystem.

Can you tell us a little bit about yourself and your team?

Michael is a programmer in a Polish bank working on a daily basis with blockchain technology. He loves programming and learning new stuff.

Piotr is a software engineer working in NuCypher. He’s interested in privacy, data, and distributed systems.

When were you first introduced to MetaMask Snaps and what was your experience like?

Piotr - Initially learned about snaps during Devcon in Osaka. The topic resurfaced earlier this year, shortly before EthDenver hackathon.

Michaeł - Piotr introduced me to Snaps, shortly after EthDenver.

What makes MetaMask Snaps different from other wallets?

An ability to access application-specific cryptography/private keys in your dapp—it enables building new user flows on top of a familiar browser-wallet experience.

Tell us about what building Snaps with MetaMask is like for you and your team?

The basics are there, but building more sophisticated snaps, especially ones that require custom UI elements, is tricky. We spent a lot of time figuring out how to fit our mental model of web developer experience into Snaps - things like deployment, testing, etc.

What does Snaps mean to you?

It is a tool which gives real power to the community, everyone for themself decides what features their MetaMask should have. Plugins allows to democratize development and enable faster growth.

What opportunities do you see with MetaMask Snaps and the possibilities it unlocks for the Web3 space?

Access to more networks (especially non-EVM ones); An ability to tap into transaction lifecycle and add custom logic (like safety checks) or customize the UX; Encrypted storage in the browser for dapp-specific sensitive content, and also an ability to “bring your own cryptography” to your users with Snaps.

Any advice you would like to share with developers keen to try MetaMask Snaps?

Compiling JS libraries in SES could be difficult, but from our experience it’s not impossible. Feedback loop in developing MetaMask is pretty long, so be sure to write unit tests.

There is a small but active developers community in MetaMask Snaps - visiting Discord, GitHub discussions etc. can be very rewarding, especially that the product is still new and there is little education avalible outside this bubble.

Building with Snaps

To get started with Snaps:

  1. Checkout the developer docs
  2. Install MetaMask Flask
  3. Check out a Snaps guide
  4. Stay connected with us on Twitter, GitHub discussions, and Discord

Keep an eye out for our team at the next hackathon in an area near you! Happy BUIDLing ⚒️

Disclaimer: This Snap was built at a hackathon. It is a proof of concept and prototype.

Receive our Newsletter