A TypeScript SDK for MetaMask's Authenticated User Storage API. Unlike E2EE user-storage, authenticated user storage holds structured JSON scoped to the authenticated user. The server can read and validate the contents, which allows other backend services to consume the data (e.g. delegation execution, notification delivery).
The SDK currently supports two domains:
- Delegations -- immutable, EIP-712 signed delegation records (list, create, revoke).
- Notification Preferences -- mutable per-user notification settings (get, put).
yarn add @metamask/authenticated-user-storage
or
npm install @metamask/authenticated-user-storage
AuthenticatedUserStorageService extends BaseDataService and requires a messenger and an environment:
messenger-- a namespaced messenger for registering actions and events. The messenger must have access toAuthenticationController:getBearerTokento retrieve access tokens.env-- selects the backend environment (DEV,UAT, orPRD).
import { Messenger } from '@metamask/messenger';
import { AuthenticatedUserStorageService } from '@metamask/authenticated-user-storage';
import type {
AuthenticatedUserStorageMessenger,
AuthenticatedUserStorageActions,
AuthenticatedUserStorageEvents,
} from '@metamask/authenticated-user-storage';
// Create the messenger
const messenger = new Messenger<
'AuthenticatedUserStorageService',
AuthenticatedUserStorageActions,
AuthenticatedUserStorageEvents
>({
namespace: 'AuthenticatedUserStorageService',
parent: rootMessenger,
});
// Instantiate the service
const service = new AuthenticatedUserStorageService({
messenger,
environment: 'prod',
});The environment option selects the backend environment:
| Value | Server |
|---|---|
'dev' |
user-storage.dev-api.cx.metamask.io |
'uat' |
user-storage.uat-api.cx.metamask.io |
'prod' |
user-storage.api.cx.metamask.io |
Once instantiated, all service methods are available as messenger actions. This allows any consumer with access to the messenger to call them without needing a direct reference to the service instance:
const delegations = await rootMessenger.call(
'AuthenticatedUserStorageService:listDelegations',
);Delegations are immutable once stored. They can only be revoked (deleted), not updated.
import type { DelegationSubmission } from '@metamask/authenticated-user-storage';
// List all delegations for the authenticated user
const delegations = await service.listDelegations();
// Submit a new signed delegation
const submission: DelegationSubmission = {
signedDelegation: { ... },
metadata: { ... },
};
await service.createDelegation(submission, 'extension');
// Revoke a delegation by its hash
await service.revokeDelegation('0xdae6d1...');Preferences are mutable. The first call creates the record; subsequent calls update it.
import type { NotificationPreferences } from '@metamask/authenticated-user-storage';
// Retrieve current preferences (returns null if none have been set)
const prefs = await service.getNotificationPreferences();
// Create or update preferences
const updated: NotificationPreferences = {
walletActivity: { ... },
marketing: { ... },
perps: { ... },
socialAI: { ... },
};
await service.putNotificationPreferences(updated, 'extension');All API responses are validated at runtime using @metamask/superstruct schemas before being returned to callers. If the server returns data that doesn't match the expected shape, the SDK throws with details about the structural mismatch rather than silently returning malformed data.
HTTP errors are represented as HttpError from @metamask/controller-utils. All errors are encouraged to bubble up to the caller. The service policy provided by BaseDataService automatically retries transient failures before propagating the error.
import { HttpError } from '@metamask/controller-utils';
try {
await service.createDelegation(submission);
} catch (error) {
if (error instanceof HttpError) {
console.error(error.message);
// e.g. "Failed to create delegation: 409"
}
}This package is part of a monorepo. Instructions for contributing can be found in the monorepo README.