Skip to content

Commit

Permalink
Document Snaps data storage (#1278)
Browse files Browse the repository at this point in the history
* Document Snaps data storage

* minor edits

* Apply suggestions from code review

Co-authored-by: Joan E <[email protected]>

* fix

---------

Co-authored-by: Joan E <[email protected]>
  • Loading branch information
alexandratran and joaniekube authored Apr 22, 2024
1 parent 59451c1 commit 7a287bf
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 31 deletions.
2 changes: 2 additions & 0 deletions docs/whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ of the [MetaMask developer page](https://metamask.io/developer/).

## April 2024

- Documented [Snaps data storage](/snaps/features/data-storage).
([#1278](https://github.com/MetaMask/metamask-docs/pull/1278))
- Documented [how to get your Snap allowlisted](/snaps/how-to/get-allowlisted).
([#1222](https://github.com/MetaMask/metamask-docs/pull/1222))
- Documented [Snaps lifecycle hooks](/snaps/features/lifecycle-hooks).
Expand Down
8 changes: 3 additions & 5 deletions snaps/features/cron-jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,8 @@ export const onCronjob: OnCronjobHandler = async ({ request }) => {
```

:::tip Access data from cron jobs
When accessing encrypted data from cron jobs using
[`snap_manageState`](../reference/snaps-api.md#snap_managestate), MetaMask requires the user to
enter their password if the wallet is locked.
When accessing [encrypted data](data-storage.md#2-use-encrypted-storage) from cron jobs, MetaMask
requires the user to enter their password if the wallet is locked.
This interaction can be confusing to the user, since the Snap accesses the data in the background
without the user being aware.

Expand All @@ -77,8 +76,7 @@ unlocked before accessing state.
This will prevent an unexpected password request, improving the user's experience.

If the cron job does not require access to sensitive data, store that data in
unencrypted state by setting `encrypted` to `false` when using
[`snap_manageState`](../reference/snaps-api.md#snap_managestate).
[unencrypted state](data-storage.md#3-use-unencrypted-storage).
:::

## Example
Expand Down
120 changes: 120 additions & 0 deletions snaps/features/data-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
description: Store encrypted and unencrypted data within a Snap.
sidebar_position: 5
---

# Data storage

You can store and manage sensitive information within a Snap using encrypted storage, or
non-sensitive information using unencrypted storage.
Use the [`snap_manageState`](../reference/snaps-api.md#snap_managestate) API method to persist up to
100 MB of data to the user's disk and retrieve it at will.
We recommend using this method for storing data in a Snap long term.

## Steps

### 1. Get permission to store data

Request the [`snap_manageState`](../reference/snaps-api.md#snap_managestate) permission.
Add the following to your Snap's manifest file:

```json title="snap.manifest.json"
"initialPermissions": {
"snap_manageState": {}
}
```

### 2. Use encrypted storage

By default, [`snap_manageState`](../reference/snaps-api.md#snap_managestate) automatically encrypts
data using a Snap-specific key before storing it on the user's disk, and automatically decrypts it
when retrieved.
This is useful to store sensitive information, such as passwords.

The following example uses `snap_manageState` to store some data using the `update` operation, and
retrieves the data at a later time using the `get` operation.
When the data is no longer required, the Snap's state is cleared using the `clear` operation.

```javascript title="index.js"
// Persist some data.
await snap.request({
method: "snap_manageState",
params: {
operation: "update",
newState: { hello: "world" },
},
});

// At a later time, get the stored data.
const persistedData = await snap.request({
method: "snap_manageState",
params: { operation: "get" },
});

console.log(persistedData);
// { hello: "world" }

// If data storage is no longer necessary, clear it.
await snap.request({
method: "snap_manageState",
params: {
operation: "clear",
},
});
```

:::tip
Accessing encrypted state requires MetaMask to be unlocked.
If you need to access encrypted state in a background task such as a [cron job](cron-jobs.md), use
[`snap_getClientStatus`](../reference/snaps-api.md#snap_getclientstatus) to ensure that MetaMask is
unlocked before accessing state, preventing an unexpected password request.
:::

### 3. Use unencrypted storage

To use unencrypted storage, set `encrypted` to `false` when storing, retrieving, or clearing data
using [`snap_manageState`](../reference/snaps-api.md#snap_managestate).
The Snap will use a storage section separate from the encrypted storage, and will not encrypt the data.
This is useful to access non-sensitive data from background operations such as
[cron jobs](cron-jobs.md), without requiring the user to enter their password in the case that
MetaMask is locked.

```javascript title="index.js"
// Persist some data.
await snap.request({
method: "snap_manageState",
params: {
operation: "update",
newState: { hello: "world" },
encrypted: false,
},
});

// At a later time, get the stored data.
const persistedData = await snap.request({
method: "snap_manageState",
params: {
operation: "get",
encrypted: false,
},
});

console.log(persistedData);
// { hello: "world" }

// If data storage is no longer necessary, clear it.
await snap.request({
method: "snap_manageState",
params: {
operation: "clear",
encrypted: false,
},
});
```

## Example

See the [`@metamask/manage-state-example-snap`](https://github.com/MetaMask/snaps/tree/main/packages/examples/packages/manage-state)
package for a full example of storing data using [`snap_manageState`](../reference/snaps-api.md#snap_managestate).
This example exposes a [custom JSON-RPC API](../learn/about-snaps/apis.md#custom-json-rpc-apis) for
dapps to store, retrieve, and clear data.
2 changes: 1 addition & 1 deletion snaps/features/lifecycle-hooks.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 5
sidebar_position: 6
description: Call an action when your Snap is installed or updated.
---

Expand Down
2 changes: 1 addition & 1 deletion snaps/features/localization.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Display your Snap's UI and metadata in the user's language.
sidebar_position: 6
sidebar_position: 7
---

# Localization
Expand Down
2 changes: 1 addition & 1 deletion snaps/features/non-evm-networks.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Manage users' non-EVM accounts and assets.
sidebar_position: 7
sidebar_position: 8
---

# Non-EVM networks
Expand Down
2 changes: 1 addition & 1 deletion snaps/features/signature-insights.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Provide insights to your users in MetaMask's signature confirmation flow.
sidebar_position: 8
sidebar_position: 9
sidebar_custom_props:
flask_only: true
---
Expand Down
2 changes: 1 addition & 1 deletion snaps/features/static-files.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Include and retrieve static files in the Snap bundle.
sidebar_position: 9
sidebar_position: 10
---

# Static files
Expand Down
12 changes: 3 additions & 9 deletions snaps/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,9 @@ The following Snaps features are available in the stable version of MetaMask:
},
{
icon: require("./assets/features/state.png").default,
href: "reference/snaps-api#snap_managestate",
title: "Encrypted storage",
description: "Securely store and manage data on the user's device."
},
{
icon: require("./assets/features/state.png").default,
href: "reference/snaps-api#snap_managestate",
title: "Unencrypted storage",
description: "Store non-sensitive data and access it while MetaMask is locked."
href: "features/data-storage",
title: "Data storage",
description: "Store encrypted and unencrypted data within a Snap."
},
{
icon: require("./assets/features/manage-keys.png").default,
Expand Down
16 changes: 4 additions & 12 deletions snaps/reference/snaps-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,9 @@ Gets the locked status of the Snaps client.

It is useful to check if MetaMask is locked in the following situations:

- When running background operations that require MetaMask to be unlocked, for example, [accessing encrypted state](#snap_managestate). If MetaMask is locked, the user gets a popup asking them to enter their password, which might be unexpected or confusing.
- When running background operations that require MetaMask to be unlocked, for example,
[accessing encrypted state](../features/data-storage.md#2-use-encrypted-storage).
If MetaMask is locked, the user gets an unexpected password request.
- When [displaying a dialog](#snap_dialog). Dialogs do not work when MetaMask is locked.

#### Returns
Expand Down Expand Up @@ -781,13 +783,6 @@ By default, the data is automatically encrypted using a Snap-specific key and au
decrypted when retrieved.
You can set `encrypted` to `false` to use unencrypted storage.

:::note
Accessing encrypted state requires MetaMask to be unlocked.
If you need to access encrypted state in a background task such as a cron job, you can use
[`snap_getClientStatus`](#snap_getclientstatus) to ensure that MetaMask is unlocked, preventing an
unexpected password request popup.
:::

#### Parameters

An object containing:
Expand All @@ -796,9 +791,6 @@ An object containing:
- `newState` - The value to update state with if the operation is `"update"`, and nothing otherwise.
- `encrypted` (optional) - Indicates whether the Snap will encrypt the data.
The default is `true`.
If set to `false`, the Snap will use a separate storage section, and will not encrypt the data.
This is useful to access the data from background operations without requiring the user to enter
their password in the case that MetaMask is locked.

#### Returns

Expand All @@ -816,7 +808,7 @@ await snap.request({
},
});

// At a later time, get the data stored.
// At a later time, get the stored data.
const persistedData = await snap.request({
method: "snap_manageState",
params: { operation: "get" },
Expand Down

0 comments on commit 7a287bf

Please sign in to comment.