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

feat: add keyring events and helper functions #74

Merged
merged 7 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@
},
"dependencies": {
"@metamask/providers": "^11.0.0",
"@metamask/snaps-controllers": "^0.38.0-flask.1",
"@metamask/snaps-utils": "^0.38.0-flask.1",
"@metamask/rpc-methods": "^0.38.1-flask.1",
"@metamask/snaps-controllers": "^0.38.2-flask.1",
"@metamask/snaps-utils": "^0.38.2-flask.1",
"@metamask/utils": "^7.0.0",
"@types/uuid": "^9.0.1",
"superstruct": "^1.0.3",
Expand Down Expand Up @@ -91,7 +92,9 @@
"@metamask/snaps-utils>@metamask/base-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>keccak": false,
"@metamask/snaps-utils>@metamask/base-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
"@metamask/snaps-utils>@metamask/permission-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>secp256k1": false,
"@metamask/rpc-methods>@metamask/permission-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>keccak": false,
"@metamask/rpc-methods>@metamask/permission-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>secp256k1": false
}
}
}
13 changes: 13 additions & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Supported keyring events.
*/
export enum KeyringEvent {
// Account events
AccountCreated = 'event:accountCreated',
AccountUpdated = 'event:accountUpdated',
AccountDeleted = 'event:accountDeleted',

// Request events
RequestApproved = 'event:requestApproved',
RequestRejected = 'event:requestRejected',
}
174 changes: 174 additions & 0 deletions src/internal/events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { is } from 'superstruct';

import {
AccountCreatedEventStruct,
AccountDeletedEventStruct,
AccountUpdatedEventStruct,
RequestApprovedEventStruct,
RequestRejectedEventStruct,
} from './events';
import { EthAccountType } from '../api';
import { KeyringEvent } from '../events';

describe('events', () => {
describe('AccountCreatedEventStruct', () => {
it('should be a valid accountCreated event', () => {
const event = {
method: KeyringEvent.AccountCreated,
params: {
account: {
id: '11027d05-12f8-4ec0-b03f-151d86a8089e',
address: '0x0123',
methods: [],
options: {},
type: EthAccountType.Eoa,
},
},
};

expect(is(event, AccountCreatedEventStruct)).toBe(true);
});

it('should be an invalid accountCreated event (invalid account id)', () => {
const event = {
method: KeyringEvent.AccountCreated,
params: {
account: {
id: 'not-a-uuid',
address: '0x0123',
methods: [],
options: {},
type: EthAccountType.Eoa,
},
},
};

expect(is(event, AccountCreatedEventStruct)).toBe(false);
});
});

describe('AccountUpdatedEventStruct', () => {
it('should be a valid accountUpdated event', () => {
const event = {
method: KeyringEvent.AccountUpdated,
params: {
account: {
id: '11027d05-12f8-4ec0-b03f-151d86a8089e',
address: '0x0123',
methods: [],
options: {},
type: EthAccountType.Eoa,
},
},
};

expect(is(event, AccountUpdatedEventStruct)).toBe(true);
});

it('should be an invalid accountUpdated event (invalid account id)', () => {
const event = {
method: KeyringEvent.AccountUpdated,
params: {
account: {
id: 'not-a-uuid',
address: '0x0123',
methods: [],
options: {},
type: EthAccountType.Eoa,
},
},
};

expect(is(event, AccountUpdatedEventStruct)).toBe(false);
});
});

describe('AccountDeletedEventStruct', () => {
it('should be a valid accountDeleted event', () => {
const event = {
method: KeyringEvent.AccountDeleted,
params: {
id: '11027d05-12f8-4ec0-b03f-151d86a8089e',
},
};

expect(is(event, AccountDeletedEventStruct)).toBe(true);
});

it('should be an invalid accountDeleted event (invalid account id)', () => {
const event = {
method: KeyringEvent.AccountDeleted,
params: {
id: 'not-a-uuid',
},
};

expect(is(event, AccountDeletedEventStruct)).toBe(false);
});
});

describe('RequestApprovedEventStruct', () => {
it('should be a valid requestApproved event', () => {
const event = {
method: KeyringEvent.RequestApproved,
params: {
id: '10423e09-c282-4d4c-8b61-3ca071a32e54',
result: {
signature: '0x0123',
},
},
};

expect(is(event, RequestApprovedEventStruct)).toBe(true);
});

it('should be an invalid requestApproved event (invalid request id)', () => {
const event = {
method: KeyringEvent.RequestApproved,
params: {
id: 'not-a-uuid',
result: {
signature: '0x0123',
},
},
};

expect(is(event, RequestApprovedEventStruct)).toBe(false);
});

it('should be an invalid requestApproved event (missing result)', () => {
const event = {
method: KeyringEvent.RequestApproved,
params: {
id: 'not-a-uuid',
},
};

expect(is(event, RequestApprovedEventStruct)).toBe(false);
});
});

describe('RequestRejectedEventStruct', () => {
it('should be a valid requestRejected event', () => {
const event = {
method: KeyringEvent.RequestRejected,
params: {
id: '10423e09-c282-4d4c-8b61-3ca071a32e54',
},
};

expect(is(event, RequestRejectedEventStruct)).toBe(true);
});

it('should be an invalid requestRejected event (invalid request id)', () => {
const event = {
method: KeyringEvent.RequestRejected,
params: {
id: 'not-a-uuid',
},
};

expect(is(event, RequestRejectedEventStruct)).toBe(false);
});
});
});
61 changes: 61 additions & 0 deletions src/internal/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { JsonStruct } from '@metamask/utils';
import { literal, object } from 'superstruct';

import { KeyringAccountStruct } from '../api';
import { KeyringEvent } from '../events';
import { UuidStruct } from '../utils';

export const AccountCreatedEventStruct = object({
method: literal(`${KeyringEvent.AccountCreated}`),
params: object({
/**
* New account object.
*/
account: KeyringAccountStruct,
}),
});

export const AccountUpdatedEventStruct = object({
method: literal(`${KeyringEvent.AccountUpdated}`),
params: object({
/**
* Updated account object.
*/
account: KeyringAccountStruct,
}),
});

export const AccountDeletedEventStruct = object({
method: literal(`${KeyringEvent.AccountDeleted}`),
params: object({
/**
* Deleted account ID.
*/
id: UuidStruct,
}),
});

export const RequestApprovedEventStruct = object({
method: literal(`${KeyringEvent.RequestApproved}`),
params: object({
/**
* Request ID.
*/
id: UuidStruct,

/**
* Request result.
*/
result: JsonStruct,
}),
});

export const RequestRejectedEventStruct = object({
method: literal(`${KeyringEvent.RequestRejected}`),
params: object({
/**
* Request ID.
*/
id: UuidStruct,
}),
});
22 changes: 22 additions & 0 deletions src/snap-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { KeyringEvent } from './events';
import { emitSnapKeyringEvent } from './snap-utils';

describe('emitSnapKeyringEvent', () => {
it('should call snap.request with the correct parameters', async () => {
const snap = {
request: jest.fn(),
};
const event = KeyringEvent.AccountDeleted;
const data = { id: 'ffa9836a-8fe4-48a2-8f0f-95d08d8c1e87' };

await emitSnapKeyringEvent(snap, event, data);

expect(snap.request).toHaveBeenCalledWith({
method: 'snap_manageAccounts',
params: {
method: event,
params: data,
},
});
});
});
25 changes: 25 additions & 0 deletions src/snap-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { SnapsGlobalObject } from '@metamask/rpc-methods';
import type { Json } from '@metamask/utils';

import type { KeyringEvent } from './events';

/**
* Emit a keyring event from a snap.
*
* @param snap - The global snap object.
* @param event - The event name.
* @param data - The event data.
*/
export async function emitSnapKeyringEvent(
snap: SnapsGlobalObject,
event: KeyringEvent,
data: Record<string, Json>,
): Promise<void> {
await snap.request({
method: 'snap_manageAccounts',
params: {
method: event,
params: { ...data },
},
});
}
Loading