Skip to content
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
2 changes: 1 addition & 1 deletion packages/examples/packages/get-entropy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ JSON-RPC methods:
`BLS12-381` elliptic curve to sign the message.

For more information, you can refer to
[the end-to-end tests](./src/index.test.ts).
[the end-to-end tests](./src/index.test.tsx).
2 changes: 1 addition & 1 deletion packages/examples/packages/get-entropy/snap.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { SnapConfig } from '@metamask/snaps-cli';
import { resolve } from 'path';

const config: SnapConfig = {
input: resolve(__dirname, 'src/index.ts'),
input: resolve(__dirname, 'src/index.tsx'),
server: {
port: 8009,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "lmkCL1vKbBcM9oIiHMkfGq1P7CATFx/fksf+kvlv+eU=",
"shasum": "JHgX6Yu2QH0wnVJ67t+IUxNpOvGOhOwWip2b6rI3F4A=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
80 changes: 0 additions & 80 deletions packages/examples/packages/get-entropy/src/index.test.ts

This file was deleted.

143 changes: 143 additions & 0 deletions packages/examples/packages/get-entropy/src/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { expect } from '@jest/globals';
import { installSnap } from '@metamask/snaps-jest';
import { Box, Copyable, Heading, Text } from '@metamask/snaps-sdk/jsx';
import { assert } from '@metamask/utils';

describe('onRpcRequest', () => {
it('throws an error if the requested method does not exist', async () => {
const { request } = await installSnap();

const response = await request({
method: 'foo',
});

expect(response).toRespondWithError({
code: -32601,
message: 'The method does not exist / is not available.',
stack: expect.any(String),
data: {
method: 'foo',
cause: null,
},
});
});

describe('signMessage', () => {
it('signs a message with the snap entropy', async () => {
const { request } = await installSnap();

const response = request({
method: 'signMessage',
params: {
message: 'Hello, world!',
},
});

const ui = await response.getInterface();
expect(ui).toRender(
<Box>
<Heading>Signature request</Heading>
<Text>
Do you want to sign the following message with Snap entropy, and the
entropy source "{'primary source'}"?
</Text>
<Copyable value="Hello, world!" />
</Box>,
);

assert(ui.type === 'confirmation');
await ui.ok();

expect(await response).toRespondWith(
'0x8b3f38050fb60fffd2e0e2ef04504b09e8f0ff46e25896cfd87ced67a5a76ac75c534c9bafbf6f38b6e50b969e1239c80916040de30a3f9ee973d6a3281d39624e7d463b2a5bc0165764b0b4ce8ad009352076c54a202a8c63554b00a46872dc',
);
});

it('signs a message with a different salt', async () => {
const { request } = await installSnap();

const response = request({
method: 'signMessage',
params: {
message: 'Hello, world!',
salt: 'Other salt',
},
});

const ui = await response.getInterface();
expect(ui).toRender(
<Box>
<Heading>Signature request</Heading>
<Text>
Do you want to sign the following message with Snap entropy, and the
entropy source "{'primary source'}"?
</Text>
<Copyable value="Hello, world!" />
</Box>,
);

assert(ui.type === 'confirmation');
await ui.ok();

expect(await response).toRespondWith(
'0x877530880baa4d1fc1fca749f5a26123275ffaa617505cae8f3da4a58d06ea43b7123d4575331dd15ffd5103ed2091050af0aa715adc3b7e122c8e07a97b7fce76c34e8e2ef0037b36015795e0ae530fed264ffb4b33bd47149af192f4c51411',
);
});

it('signs a message with a different entropy source', async () => {
const { request } = await installSnap();

const response = request({
method: 'signMessage',
params: {
message: 'Hello, world!',
source: 'alternative',
},
});

const ui = await response.getInterface();
expect(ui).toRender(
<Box>
<Heading>Signature request</Heading>
<Text>
Do you want to sign the following message with Snap entropy, and the
entropy source "{'Alternative Secret Recovery Phrase'}"?
</Text>
<Copyable value="Hello, world!" />
</Box>,
);

assert(ui.type === 'confirmation');
await ui.ok();

expect(await response).toRespondWith(
'0xad9bff2fc10e412b1dc3e2f88bc2c0da3c994c4a75cd59b1a92ef18bfd24af459aad5a6355d3030cf44cd52486dc274419177820fdc44b86842a043a3da5aa3a5c07990265891dc871a7cd341b1771282aa042a024810f17ecb6929d731a4013',
);
});
});

describe('getEntropySources', () => {
it('returns the entropy sources', async () => {
const { request } = await installSnap();

const response = request({
method: 'getEntropySources',
});

expect(await response).toRespondWith([
{
id: 'default',
name: 'Default Secret Recovery Phrase',
type: 'mnemonic',
primary: true,
},
{
id: 'alternative',
name: 'Alternative Secret Recovery Phrase',
type: 'mnemonic',
primary: false,
},
]);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import type { OnRpcRequestHandler } from '@metamask/snaps-sdk';
import {
DialogType,
panel,
text,
heading,
copyable,
UserRejectedRequestError,
MethodNotFoundError,
UserRejectedRequestError,
} from '@metamask/snaps-sdk';
import { Box, Copyable, Heading, Text } from '@metamask/snaps-sdk/jsx';
import { bytesToHex, stringToBytes } from '@metamask/utils';
import { sign } from '@noble/bls12-381';

import type { SignMessageParams } from './types';
import { getEntropy } from './utils';
import { getEntropy, getEntropySourceName } from './utils';

/**
* Handle incoming JSON-RPC requests from the dapp, sent through the
Expand All @@ -31,31 +28,40 @@ import { getEntropy } from './utils';
export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
switch (request.method) {
case 'signMessage': {
const { message, salt } = request.params as SignMessageParams;
const { message, salt, source } = request.params as SignMessageParams;

const approved = await snap.request({
method: 'snap_dialog',
params: {
type: DialogType.Confirmation,
content: panel([
heading('Signature request'),
text(
'Do you want to sign the following message with snap entropy?',
),
copyable(message),
]),
content: (
<Box>
<Heading>Signature request</Heading>
<Text>
Do you want to sign the following message with Snap entropy, and
the entropy source "{await getEntropySourceName(source)}"?
</Text>
<Copyable value={message} />
</Box>
),
},
});

if (!approved) {
throw new UserRejectedRequestError();
}

const privateKey = await getEntropy(salt);
const privateKey = await getEntropy(salt, source);
const newLocal = await sign(stringToBytes(message), privateKey);
return bytesToHex(newLocal);
}

case 'getEntropySources': {
return await snap.request({
method: 'snap_listEntropySources',
});
}

default:
throw new MethodNotFoundError({ method: request.method });
}
Expand Down
6 changes: 6 additions & 0 deletions packages/examples/packages/get-entropy/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@ export type SignMessageParams = {
* Defaults to "Signing key".
*/
salt?: string;

/**
* The entropy source to use for the signature. If not provided, the primary
* entropy source will be used.
*/
source?: string | undefined;
};
39 changes: 38 additions & 1 deletion packages/examples/packages/get-entropy/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,54 @@ import { remove0x } from '@metamask/utils';
*
* @param salt - The salt to use for the entropy derivation. Using a different
* salt will result in completely different entropy being generated.
* @param source - The entropy source to use for the entropy derivation. If not
* provided, the primary entropy source will be used.
* @returns The generated entropy, without the leading "0x".
* @see https://docs.metamask.io/snaps/reference/rpc-api/#snap_getentropy
*/
export async function getEntropy(salt = 'Signing key') {
export async function getEntropy(
salt = 'Signing key',
source?: string | undefined,
) {
const entropy = await snap.request({
method: 'snap_getEntropy',
params: {
version: 1,
salt,
source,
},
});

return remove0x(entropy);
}

/**
* Get the name of an entropy source by its ID using the
* `snap_listEntropySources` JSON-RPC method.
*
* If the ID is not provided, the name of the primary entropy source will be
* returned.
*
* @param id - The ID of the entropy source.
* @returns The name of the entropy source.
*/
export async function getEntropySourceName(id?: string | undefined) {
if (id) {
const sources = await snap.request({
method: 'snap_listEntropySources',
});

const source = sources.find((item) => item.id === id);
if (source) {
if (source.name.length > 0) {
return source.name;
}

return source.id;
}

return 'unknown source';
}

return 'primary source';
}
Loading
Loading