# Getting Started with MetaMask Snaps
_Devcon VI Workshop_
Kudos to [@Mrtenz](https://github.com/Mrtenz) for the Snap implementation.
## Resources
- [Snaps landing page](https://metamask.io/snaps)
- [Snaps documentation](https://docs.metamask.io/guide/snaps.html)
- [Snaps discussion board](https://github.com/MetaMask/snaps-monorepo/discussions)
- [Template snap monorepo](https://github.com/MetaMask/template-snap-monorepo)
- [Completed workshop code](https://github.com/rekmarks/devcon-2022-snap/tree/completed)
## Before We Begin
1. Ensure that you have the following dependencies:
- A Chromium browser or Firefox
- `git`
- [Node.js](https://nodejs.org/en/) `^16.0.0`
- [`yarn`](https://yarnpkg.com/getting-started/install)
- `yarn` can also be installed after cloning the repository, see below.
2. Install [MetaMask Flask](https://metamask.io/flask/).
- _During Devcon VI, a prerelease build of MetaMask Flask was required, but this is no longer the case._
3. Add an existing secret recovery phrase that you aren't afraid to lose, or create a new one.
## The Workshop
Our goal is to create a snap that decodes contract call function names and parameters using the [4byte API](https://www.4byte.directory/docs/) and [`@metamask/abi-utils`](https://npmjs.com/package/@metamask/abi-utils).
1. Create a new GitHub repository using the [template snap monorepo](https://github.com/MetaMask/template-snap-monorepo).
- This template contains two workspaces, one for the snap and one for the website / dapp that will serve as its user interface.
- `git clone` your new repository.
- Run `yarn install` at the monorepo root.
- If you do not already have `yarn` installed, you can start the installation by directly running `.yarn/releases/yarn-3.2.1.cjs install` from the monorepo root.
2. Boot the snap and website by running `yarn start` at the monorepo root.
- This will set up live reloading for both the snap and the website.
3. Install the example snap by clicking `Connect` on the website, then try out its functionality by clicking `Send message`.
- The snap will be executed by MetaMask in a sandboxed environment and display a
confirmation.
4. Rewrite the snap to use the transaction insights API.
- In `snap.manifest.json`, replace the `snap_confirm` permission with `endowment:transaction-insight`.
- In `index.ts`, replace the `onRpcRequest` handler with an `onTransaction` handler. Make sure it is exported.
- We will add some basic validation. Add [`@metamask/utils`](https://npmjs.com/package/@metamask/utils) as a dependency of your snap and import it in your snap's `index.ts` file.
5. Customize the website to suit your snap, and rewrite the `Send message` logic to create an Ethereum transaction instead.
- Don't forget to add logic for requesting Ethereum accounts so that you are able to submit transactions to MetaMask.
- You can use the address from `TransactionConstants` as the recipient address. See [Transaction Snippets](#Transaction-Snippets) below.
6. Click `Reconnect` in the UI to reinstall the snap, then click `Send message` to observe your "decoded transaction".
7. Call the 4byte API from within your snap.
- Snaps do not get network access by default. Add `endowment:network-access` to your snap's permissions.
- Copy the [4byte Snippet](#4byte-Snippet) below for the API URL and some helpful types.
- Return some of the data from 4byte as an "insight" so we know that it works.
8. Finally, decode the parameters using [`@metamask/abi-utils`](https://npmjs.com/package/@metamask/abi-utils).
- Add `@metamask/abi-utils` as a dependency of your snap and import it in your snap's `index.ts` file.
- Copy the `normalizeAbiValue` function from the [Value Normalizer Snippet](#Value-Normalizer-Snippet) and paste it into your snap's `index.ts` file.
- Extract the parameter types, decode them using `@metamaks/abi-utils`, and return your your completed insights.
### Transaction Snippets
We will demonstrate our snap's functionality by decoding some mock contract transactions that we create. You can of course try some contract interactions on your own as well.
```typescript
enum TransactionConstants {
// The address of an arbitrary contract that will reject any transactions it receives
Address = '0x08A8fDBddc160A7d5b957256b903dCAb1aE512C5',
// Some example encoded contract transaction data
UpdateWithdrawalAccount = '0x83ade3dc00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000047170ceae335a9db7e96b72de630389669b334710000000000000000000000006b175474e89094c44da98b954eedeac495271d0f',
UpdateMigrationMode = '0x2e26065e0000000000000000000000000000000000000000000000000000000000000000',
UpdateCap = '0x85b2c14a00000000000000000000000047170ceae335a9db7e96b72de630389669b334710000000000000000000000000000000000000000000000000de0b6b3a7640000',
}
```
```typescript
// A function that sends contract transactions
export const sendContractTransaction = async (data: string) => {
// Get the user's account from MetaMask.
const [from] = (await window.ethereum.request({
method: 'eth_requestAccounts',
})) as string[];
// Send a transaction to MetaMask.
await window.ethereum.request({
method: 'eth_sendTransaction',
params: [
{
from,
to: TransactionConstants.Address,
value: '0x0',
data,
},
],
});
};
```
### 4byte Snippet
We will use the [4byte API](https://www.4byte.directory/docs/) in our Snap. Here is a snippet with some constants:
```typescript
// The API endpoint to get a list of functions by 4 byte signature.
const API_ENDPOINT =
'https://www.4byte.directory/api/v1/signatures/?hex_signature=';
/* eslint-disable camelcase */
type FourByteSignature = {
id: number;
created_at: string;
text_signature: string;
hex_signature: string;
bytes_signature: string;
};
/* eslint-enable camelcase */
```
### Value Normalizer Snippet
```typescript
/**
* The ABI decoder returns certain which are not JSON serializable. This
* function converts them to strings.
*
* @param value - The value to convert.
* @returns The converted value.
*/
function normalizeAbiValue(value: unknown): Json {
if (Array.isArray(value)) {
return value.map(normalizeAbiValue);
}
if (value instanceof Uint8Array) {
return bytesToHex(value);
}
if (typeof value === 'bigint') {
return value.toString();
}
return value as Json;
}
```