Skip to content
This repository has been archived by the owner on Oct 7, 2024. It is now read-only.

Commit

Permalink
feat: add KeyringSnapControllerClient class (#203)
Browse files Browse the repository at this point in the history
This commit add the `KeyringSnapControllerClient` to this package since
it's being removed from the `keyring-api`.

See: <MetaMask/keyring-api#241>
  • Loading branch information
danroc authored Jan 19, 2024
1 parent b8dd692 commit 7f255d0
Show file tree
Hide file tree
Showing 5 changed files with 422 additions and 105 deletions.
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
"@ethereumjs/tx": "^4.2.0",
"@metamask/eth-sig-util": "^7.0.0",
"@metamask/keyring-api": "^2.0.0",
"@metamask/snaps-controllers": "^3.4.1",
"@metamask/snaps-sdk": "^1.2.0",
"@metamask/snaps-controllers": "^4.1.0",
"@metamask/snaps-sdk": "^1.4.0",
"@metamask/snaps-utils": "^5.2.0",
"@metamask/utils": "^8.1.0",
"@types/uuid": "^9.0.1",
"superstruct": "^1.0.3",
Expand Down Expand Up @@ -75,8 +76,8 @@
},
"lavamoat": {
"allowScripts": {
"@metamask/snaps-controllers>@metamask/phishing-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>keccak": false,
"@metamask/snaps-controllers>@metamask/phishing-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>secp256k1": false
"@metamask/snaps-utils>@metamask/permission-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>keccak": false,
"@metamask/snaps-utils>@metamask/permission-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>secp256k1": false
}
}
}
82 changes: 82 additions & 0 deletions src/KeyringSnapControllerClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type { KeyringAccount } from '@metamask/keyring-api';
import type { SnapController } from '@metamask/snaps-controllers';
import type { SnapId } from '@metamask/snaps-sdk';

import { KeyringSnapControllerClient } from './KeyringSnapControllerClient';

describe('KeyringSnapControllerClient', () => {
const snapId = 'local:localhost:3000' as SnapId;

const accountsList: KeyringAccount[] = [
{
id: '13f94041-6ae6-451f-a0fe-afdd2fda18a7',
address: '0xE9A74AACd7df8112911ca93260fC5a046f8a64Ae',
options: {},
methods: [],
type: 'eip155:eoa',
},
];

const controller = {
handleRequest: jest.fn(),
};

describe('listAccounts', () => {
const request = {
snapId,
origin: 'metamask',
handler: 'onKeyringRequest',
request: {
id: expect.any(String),
jsonrpc: '2.0',
method: 'keyring_listAccounts',
},
};

it('should call the listAccounts method and return the result', async () => {
const client = new KeyringSnapControllerClient({
controller: controller as unknown as SnapController,
snapId,
});

controller.handleRequest.mockResolvedValue(accountsList);
const accounts = await client.listAccounts();
expect(controller.handleRequest).toHaveBeenCalledWith(request);
expect(accounts).toStrictEqual(accountsList);
});

it('should call the listAccounts method and return the result (withSnapId)', async () => {
const client = new KeyringSnapControllerClient({
controller: controller as unknown as SnapController,
});

controller.handleRequest.mockResolvedValue(accountsList);
const accounts = await client.withSnapId(snapId).listAccounts();
expect(controller.handleRequest).toHaveBeenCalledWith(request);
expect(accounts).toStrictEqual(accountsList);
});

it('should call the default snapId value ("undefined")', async () => {
const client = new KeyringSnapControllerClient({
controller: controller as unknown as SnapController,
});

controller.handleRequest.mockResolvedValue(accountsList);
await client.listAccounts();
expect(controller.handleRequest).toHaveBeenCalledWith({
...request,
snapId: 'undefined',
});
});
});

describe('getController', () => {
it('should return the controller', () => {
const client = new KeyringSnapControllerClient({
controller: controller as unknown as SnapController,
});

expect(client.getController()).toBe(controller);
});
});
});
115 changes: 115 additions & 0 deletions src/KeyringSnapControllerClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { KeyringClient, type Sender } from '@metamask/keyring-api';
import type { JsonRpcRequest } from '@metamask/keyring-api/dist/JsonRpcRequest';
import type { SnapController } from '@metamask/snaps-controllers';
import type { SnapId } from '@metamask/snaps-sdk';
import type { HandlerType } from '@metamask/snaps-utils';
import type { Json } from '@metamask/utils';

/**
* Implementation of the `Sender` interface that can be used to send requests
* to a snap through a `SnapController`.
*/
class SnapControllerSender implements Sender {
#snapId: SnapId;

#origin: string;

#controller: SnapController;

#handler: HandlerType;

/**
* Create a new instance of `SnapControllerSender`.
*
* @param controller - The `SnapController` instance to send requests to.
* @param snapId - The ID of the snap to use.
* @param origin - The sender's origin.
* @param handler - The handler type.
*/
constructor(
controller: any,
snapId: SnapId,
origin: string,
handler: HandlerType,
) {
this.#controller = controller;
this.#snapId = snapId;
this.#origin = origin;
this.#handler = handler;
}

/**
* Send a request to the snap and return the response.
*
* @param request - JSON-RPC request to send to the snap.
* @returns A promise that resolves to the response of the request.
*/
async send(request: JsonRpcRequest): Promise<Json> {
return this.#controller.handleRequest({
snapId: this.#snapId,
origin: this.#origin,
handler: this.#handler,
request,
}) as Promise<Json>;
}
}

/**
* A `KeyringClient` that allows the communication with a snap through the
* `SnapController`.
*/
export class KeyringSnapControllerClient extends KeyringClient {
#controller: SnapController;

/**
* Create a new instance of `KeyringSnapControllerClient`.
*
* The `handlerType` argument has a hard-coded default `string` value instead
* of a `HandlerType` value to prevent the `@metamask/snaps-utils` module
* from being required at runtime.
*
* @param args - Constructor arguments.
* @param args.controller - The `SnapController` instance to use.
* @param args.snapId - The ID of the snap to use (default: `'undefined'`).
* @param args.origin - The sender's origin (default: `'metamask'`).
* @param args.handler - The handler type (default: `'onKeyringRequest'`).
*/
constructor({
controller,
snapId = 'undefined' as SnapId,
origin = 'metamask',
handler = 'onKeyringRequest' as HandlerType,
}: {
controller: SnapController;
snapId?: SnapId;
origin?: string;
handler?: HandlerType;
}) {
super(new SnapControllerSender(controller, snapId, origin, handler));
this.#controller = controller;
}

/**
* Create a new instance of `KeyringSnapControllerClient` with the specified
* `snapId`.
*
* @param snapId - The ID of the snap to use in the new instance.
* @returns A new instance of `KeyringSnapControllerClient` with the
* specified snap ID.
*/
withSnapId(snapId: SnapId): KeyringSnapControllerClient {
return new KeyringSnapControllerClient({
controller: this.#controller,
snapId,
});
}

/**
* Get the `SnapController` instance used by this client.
*
* @returns The `SnapController` instance used by this client.
*/
getController(): SnapController {
return this.#controller;
}
}
2 changes: 1 addition & 1 deletion src/SnapKeyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
EthMethod,
EthUserOperationPatchStruct,
KeyringEvent,
KeyringSnapControllerClient,
RequestApprovedEventStruct,
RequestRejectedEventStruct,
} from '@metamask/keyring-api';
Expand All @@ -32,6 +31,7 @@ import { assert, mask, object, string } from 'superstruct';
import { v4 as uuid } from 'uuid';

import { DeferredPromise } from './DeferredPromise';
import { KeyringSnapControllerClient } from './KeyringSnapControllerClient';
import { SnapIdMap } from './SnapIdMap';
import type { SnapMessage } from './types';
import { SnapMessageStruct } from './types';
Expand Down
Loading

0 comments on commit 7f255d0

Please sign in to comment.