Skip to content

Commit 67adcef

Browse files
authored
Implement SIP-30 (#3156)
This implements the JSON-RPC methods and changes as described in [SIP-30](https://metamask.github.io/SIPs/SIPS/sip-30). Extension implementation: MetaMask/metamask-extension#30554. Closes #2981.
1 parent c1a902e commit 67adcef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1015
-147
lines changed

packages/examples/packages/get-entropy/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ JSON-RPC methods:
3232
`BLS12-381` elliptic curve to sign the message.
3333

3434
For more information, you can refer to
35-
[the end-to-end tests](./src/index.test.ts).
35+
[the end-to-end tests](./src/index.test.tsx).

packages/examples/packages/get-entropy/snap.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { SnapConfig } from '@metamask/snaps-cli';
22
import { resolve } from 'path';
33

44
const config: SnapConfig = {
5-
input: resolve(__dirname, 'src/index.ts'),
5+
input: resolve(__dirname, 'src/index.tsx'),
66
server: {
77
port: 8009,
88
},

packages/examples/packages/get-entropy/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "lmkCL1vKbBcM9oIiHMkfGq1P7CATFx/fksf+kvlv+eU=",
10+
"shasum": "JHgX6Yu2QH0wnVJ67t+IUxNpOvGOhOwWip2b6rI3F4A=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/get-entropy/src/index.test.ts

Lines changed: 0 additions & 80 deletions
This file was deleted.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { expect } from '@jest/globals';
2+
import { installSnap } from '@metamask/snaps-jest';
3+
import { Box, Copyable, Heading, Text } from '@metamask/snaps-sdk/jsx';
4+
import { assert } from '@metamask/utils';
5+
6+
describe('onRpcRequest', () => {
7+
it('throws an error if the requested method does not exist', async () => {
8+
const { request } = await installSnap();
9+
10+
const response = await request({
11+
method: 'foo',
12+
});
13+
14+
expect(response).toRespondWithError({
15+
code: -32601,
16+
message: 'The method does not exist / is not available.',
17+
stack: expect.any(String),
18+
data: {
19+
method: 'foo',
20+
cause: null,
21+
},
22+
});
23+
});
24+
25+
describe('signMessage', () => {
26+
it('signs a message with the snap entropy', async () => {
27+
const { request } = await installSnap();
28+
29+
const response = request({
30+
method: 'signMessage',
31+
params: {
32+
message: 'Hello, world!',
33+
},
34+
});
35+
36+
const ui = await response.getInterface();
37+
expect(ui).toRender(
38+
<Box>
39+
<Heading>Signature request</Heading>
40+
<Text>
41+
Do you want to sign the following message with Snap entropy, and the
42+
entropy source "{'primary source'}"?
43+
</Text>
44+
<Copyable value="Hello, world!" />
45+
</Box>,
46+
);
47+
48+
assert(ui.type === 'confirmation');
49+
await ui.ok();
50+
51+
expect(await response).toRespondWith(
52+
'0x8b3f38050fb60fffd2e0e2ef04504b09e8f0ff46e25896cfd87ced67a5a76ac75c534c9bafbf6f38b6e50b969e1239c80916040de30a3f9ee973d6a3281d39624e7d463b2a5bc0165764b0b4ce8ad009352076c54a202a8c63554b00a46872dc',
53+
);
54+
});
55+
56+
it('signs a message with a different salt', async () => {
57+
const { request } = await installSnap();
58+
59+
const response = request({
60+
method: 'signMessage',
61+
params: {
62+
message: 'Hello, world!',
63+
salt: 'Other salt',
64+
},
65+
});
66+
67+
const ui = await response.getInterface();
68+
expect(ui).toRender(
69+
<Box>
70+
<Heading>Signature request</Heading>
71+
<Text>
72+
Do you want to sign the following message with Snap entropy, and the
73+
entropy source "{'primary source'}"?
74+
</Text>
75+
<Copyable value="Hello, world!" />
76+
</Box>,
77+
);
78+
79+
assert(ui.type === 'confirmation');
80+
await ui.ok();
81+
82+
expect(await response).toRespondWith(
83+
'0x877530880baa4d1fc1fca749f5a26123275ffaa617505cae8f3da4a58d06ea43b7123d4575331dd15ffd5103ed2091050af0aa715adc3b7e122c8e07a97b7fce76c34e8e2ef0037b36015795e0ae530fed264ffb4b33bd47149af192f4c51411',
84+
);
85+
});
86+
87+
it('signs a message with a different entropy source', async () => {
88+
const { request } = await installSnap();
89+
90+
const response = request({
91+
method: 'signMessage',
92+
params: {
93+
message: 'Hello, world!',
94+
source: 'alternative',
95+
},
96+
});
97+
98+
const ui = await response.getInterface();
99+
expect(ui).toRender(
100+
<Box>
101+
<Heading>Signature request</Heading>
102+
<Text>
103+
Do you want to sign the following message with Snap entropy, and the
104+
entropy source "{'Alternative Secret Recovery Phrase'}"?
105+
</Text>
106+
<Copyable value="Hello, world!" />
107+
</Box>,
108+
);
109+
110+
assert(ui.type === 'confirmation');
111+
await ui.ok();
112+
113+
expect(await response).toRespondWith(
114+
'0xad9bff2fc10e412b1dc3e2f88bc2c0da3c994c4a75cd59b1a92ef18bfd24af459aad5a6355d3030cf44cd52486dc274419177820fdc44b86842a043a3da5aa3a5c07990265891dc871a7cd341b1771282aa042a024810f17ecb6929d731a4013',
115+
);
116+
});
117+
});
118+
119+
describe('getEntropySources', () => {
120+
it('returns the entropy sources', async () => {
121+
const { request } = await installSnap();
122+
123+
const response = request({
124+
method: 'getEntropySources',
125+
});
126+
127+
expect(await response).toRespondWith([
128+
{
129+
id: 'default',
130+
name: 'Default Secret Recovery Phrase',
131+
type: 'mnemonic',
132+
primary: true,
133+
},
134+
{
135+
id: 'alternative',
136+
name: 'Alternative Secret Recovery Phrase',
137+
type: 'mnemonic',
138+
primary: false,
139+
},
140+
]);
141+
});
142+
});
143+
});

packages/examples/packages/get-entropy/src/index.ts renamed to packages/examples/packages/get-entropy/src/index.tsx

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import type { OnRpcRequestHandler } from '@metamask/snaps-sdk';
22
import {
33
DialogType,
4-
panel,
5-
text,
6-
heading,
7-
copyable,
8-
UserRejectedRequestError,
94
MethodNotFoundError,
5+
UserRejectedRequestError,
106
} from '@metamask/snaps-sdk';
7+
import { Box, Copyable, Heading, Text } from '@metamask/snaps-sdk/jsx';
118
import { bytesToHex, stringToBytes } from '@metamask/utils';
129
import { sign } from '@noble/bls12-381';
1310

1411
import type { SignMessageParams } from './types';
15-
import { getEntropy } from './utils';
12+
import { getEntropy, getEntropySourceName } from './utils';
1613

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

3633
const approved = await snap.request({
3734
method: 'snap_dialog',
3835
params: {
3936
type: DialogType.Confirmation,
40-
content: panel([
41-
heading('Signature request'),
42-
text(
43-
'Do you want to sign the following message with snap entropy?',
44-
),
45-
copyable(message),
46-
]),
37+
content: (
38+
<Box>
39+
<Heading>Signature request</Heading>
40+
<Text>
41+
Do you want to sign the following message with Snap entropy, and
42+
the entropy source "{await getEntropySourceName(source)}"?
43+
</Text>
44+
<Copyable value={message} />
45+
</Box>
46+
),
4747
},
4848
});
4949

5050
if (!approved) {
5151
throw new UserRejectedRequestError();
5252
}
5353

54-
const privateKey = await getEntropy(salt);
54+
const privateKey = await getEntropy(salt, source);
5555
const newLocal = await sign(stringToBytes(message), privateKey);
5656
return bytesToHex(newLocal);
5757
}
5858

59+
case 'getEntropySources': {
60+
return await snap.request({
61+
method: 'snap_listEntropySources',
62+
});
63+
}
64+
5965
default:
6066
throw new MethodNotFoundError({ method: request.method });
6167
}

packages/examples/packages/get-entropy/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ export type SignMessageParams = {
1515
* Defaults to "Signing key".
1616
*/
1717
salt?: string;
18+
19+
/**
20+
* The entropy source to use for the signature. If not provided, the primary
21+
* entropy source will be used.
22+
*/
23+
source?: string | undefined;
1824
};

packages/examples/packages/get-entropy/src/utils.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,54 @@ import { remove0x } from '@metamask/utils';
1313
*
1414
* @param salt - The salt to use for the entropy derivation. Using a different
1515
* salt will result in completely different entropy being generated.
16+
* @param source - The entropy source to use for the entropy derivation. If not
17+
* provided, the primary entropy source will be used.
1618
* @returns The generated entropy, without the leading "0x".
1719
* @see https://docs.metamask.io/snaps/reference/rpc-api/#snap_getentropy
1820
*/
19-
export async function getEntropy(salt = 'Signing key') {
21+
export async function getEntropy(
22+
salt = 'Signing key',
23+
source?: string | undefined,
24+
) {
2025
const entropy = await snap.request({
2126
method: 'snap_getEntropy',
2227
params: {
2328
version: 1,
2429
salt,
30+
source,
2531
},
2632
});
2733

2834
return remove0x(entropy);
2935
}
36+
37+
/**
38+
* Get the name of an entropy source by its ID using the
39+
* `snap_listEntropySources` JSON-RPC method.
40+
*
41+
* If the ID is not provided, the name of the primary entropy source will be
42+
* returned.
43+
*
44+
* @param id - The ID of the entropy source.
45+
* @returns The name of the entropy source.
46+
*/
47+
export async function getEntropySourceName(id?: string | undefined) {
48+
if (id) {
49+
const sources = await snap.request({
50+
method: 'snap_listEntropySources',
51+
});
52+
53+
const source = sources.find((item) => item.id === id);
54+
if (source) {
55+
if (source.name.length > 0) {
56+
return source.name;
57+
}
58+
59+
return source.id;
60+
}
61+
62+
return 'unknown source';
63+
}
64+
65+
return 'primary source';
66+
}

0 commit comments

Comments
 (0)