Skip to content

Commit 8e418f6

Browse files
feat: Add onAssetHistoricalPrice handler (#3282)
This PR adds a new handler called `onAssetHistoricalPrice`. Small updates bundled in this PR: - Create a util function `assertRequestArguments` in the execution-environment validation to remove code duplication. Progresses: #3229 --------- Co-authored-by: Frederik Bolding <[email protected]>
1 parent 48f76da commit 8e418f6

File tree

20 files changed

+662
-52
lines changed

20 files changed

+662
-52
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"branches": 93.41,
2+
"branches": 93.42,
33
"functions": 97.38,
44
"lines": 98.34,
5-
"statements": 98.07
5+
"statements": 98.08
66
}

packages/snaps-controllers/src/snaps/SnapController.test.tsx

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4354,6 +4354,145 @@ describe('SnapController', () => {
43544354
});
43554355
});
43564356

4357+
describe('onAssetHistoricalPrice', () => {
4358+
it('throws if `onAssetHistoricalPrice` handler returns an invalid response', async () => {
4359+
const rootMessenger = getControllerMessenger();
4360+
const messenger = getSnapControllerMessenger(rootMessenger);
4361+
const snapController = getSnapController(
4362+
getSnapControllerOptions({
4363+
messenger,
4364+
state: {
4365+
snaps: getPersistedSnapsState(),
4366+
},
4367+
}),
4368+
);
4369+
4370+
rootMessenger.registerActionHandler(
4371+
'PermissionController:getPermissions',
4372+
() => ({
4373+
[SnapEndowments.Assets]: {
4374+
caveats: [
4375+
{
4376+
type: SnapCaveatType.ChainIds,
4377+
value: ['bip122:000000000019d6689c085ae165831e93'],
4378+
},
4379+
],
4380+
date: 1664187844588,
4381+
id: 'izn0WGUO8cvq_jqvLQuQP',
4382+
invoker: MOCK_SNAP_ID,
4383+
parentCapability: SnapEndowments.Assets,
4384+
},
4385+
}),
4386+
);
4387+
4388+
rootMessenger.registerActionHandler(
4389+
'SubjectMetadataController:getSubjectMetadata',
4390+
() => MOCK_SNAP_SUBJECT_METADATA,
4391+
);
4392+
4393+
rootMessenger.registerActionHandler(
4394+
'ExecutionService:handleRpcRequest',
4395+
async () =>
4396+
Promise.resolve({
4397+
historicalPrice: { foo: {} },
4398+
}),
4399+
);
4400+
4401+
await expect(
4402+
snapController.handleRequest({
4403+
snapId: MOCK_SNAP_ID,
4404+
origin: MOCK_ORIGIN,
4405+
handler: HandlerType.OnAssetHistoricalPrice,
4406+
request: {
4407+
jsonrpc: '2.0',
4408+
method: ' ',
4409+
params: {},
4410+
id: 1,
4411+
},
4412+
}),
4413+
).rejects.toThrow(
4414+
`Assertion failed: At path: historicalPrice.intervals -- Expected an object, but received: undefined.`,
4415+
);
4416+
4417+
snapController.destroy();
4418+
});
4419+
4420+
it('returns the value when `onAssetHistoricalPrice` returns a valid response', async () => {
4421+
const rootMessenger = getControllerMessenger();
4422+
const messenger = getSnapControllerMessenger(rootMessenger);
4423+
const snapController = getSnapController(
4424+
getSnapControllerOptions({
4425+
messenger,
4426+
state: {
4427+
snaps: getPersistedSnapsState(),
4428+
},
4429+
}),
4430+
);
4431+
4432+
rootMessenger.registerActionHandler(
4433+
'PermissionController:getPermissions',
4434+
() => ({
4435+
[SnapEndowments.Assets]: {
4436+
caveats: [
4437+
{
4438+
type: SnapCaveatType.ChainIds,
4439+
value: ['bip122:000000000019d6689c085ae165831e93'],
4440+
},
4441+
],
4442+
date: 1664187844588,
4443+
id: 'izn0WGUO8cvq_jqvLQuQP',
4444+
invoker: MOCK_SNAP_ID,
4445+
parentCapability: SnapEndowments.Assets,
4446+
},
4447+
}),
4448+
);
4449+
4450+
rootMessenger.registerActionHandler(
4451+
'SubjectMetadataController:getSubjectMetadata',
4452+
() => MOCK_SNAP_SUBJECT_METADATA,
4453+
);
4454+
4455+
rootMessenger.registerActionHandler(
4456+
'ExecutionService:handleRpcRequest',
4457+
async () =>
4458+
Promise.resolve({
4459+
historicalPrice: {
4460+
intervals: {
4461+
P1D: [[1737548790, '400']],
4462+
},
4463+
updateTime: 1737548790,
4464+
},
4465+
}),
4466+
);
4467+
4468+
expect(
4469+
await snapController.handleRequest({
4470+
snapId: MOCK_SNAP_ID,
4471+
origin: MOCK_ORIGIN,
4472+
handler: HandlerType.OnAssetHistoricalPrice,
4473+
request: {
4474+
jsonrpc: '2.0',
4475+
method: ' ',
4476+
params: {
4477+
from: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
4478+
to: 'swift:0/iso4217:USD',
4479+
},
4480+
id: 1,
4481+
},
4482+
}),
4483+
).toStrictEqual({
4484+
historicalPrice: {
4485+
intervals: {
4486+
P1D: [[1737548790, '400']],
4487+
},
4488+
updateTime: 1737548790,
4489+
},
4490+
});
4491+
4492+
snapController.destroy();
4493+
});
4494+
});
4495+
43574496
describe('getRpcRequestHandler', () => {
43584497
it('handlers populate the "jsonrpc" property if missing', async () => {
43594498
const rootMessenger = getControllerMessenger();

packages/snaps-controllers/src/snaps/SnapController.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ import {
104104
MAX_FILE_SIZE,
105105
OnSettingsPageResponseStruct,
106106
isValidUrl,
107+
OnAssetHistoricalPriceResponseStruct,
107108
} from '@metamask/snaps-utils';
108109
import type {
109110
Json,
@@ -3820,6 +3821,9 @@ export class SnapController extends BaseController<
38203821
case HandlerType.OnAssetsConversion:
38213822
assertStruct(result, OnAssetsConversionResponseStruct);
38223823
break;
3824+
case HandlerType.OnAssetHistoricalPrice:
3825+
assertStruct(result, OnAssetHistoricalPriceResponseStruct);
3826+
break;
38233827
default:
38243828
break;
38253829
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"branches": 80.53,
3-
"functions": 88.96,
4-
"lines": 90.69,
5-
"statements": 89.74
2+
"branches": 80.66,
3+
"functions": 89.1,
4+
"lines": 90.78,
5+
"statements": 89.72
66
}

packages/snaps-execution-environments/src/common/BaseSnapExecutor.test.browser.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,6 +1473,60 @@ describe('BaseSnapExecutor', () => {
14731473
});
14741474
});
14751475

1476+
it('supports `onAssetHistoricalPrice` export', async () => {
1477+
const CODE = `
1478+
module.exports.onAssetHistoricalPrice = () => ({ historicalPrice: {
1479+
intervals: {
1480+
'P1D': [
1481+
[1635724800000, "1"],
1482+
]
1483+
},
1484+
updateTime: 1635724800000,
1485+
} });
1486+
`;
1487+
1488+
const executor = new TestSnapExecutor();
1489+
await executor.executeSnap(1, MOCK_SNAP_ID, CODE, []);
1490+
1491+
expect(await executor.readCommand()).toStrictEqual({
1492+
jsonrpc: '2.0',
1493+
id: 1,
1494+
result: 'OK',
1495+
});
1496+
1497+
await executor.writeCommand({
1498+
jsonrpc: '2.0',
1499+
id: 2,
1500+
method: 'snapRpc',
1501+
params: [
1502+
MOCK_SNAP_ID,
1503+
HandlerType.OnAssetHistoricalPrice,
1504+
MOCK_ORIGIN,
1505+
{
1506+
jsonrpc: '2.0',
1507+
method: '',
1508+
params: {
1509+
from: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
1510+
to: 'swift:0/iso4217:USD',
1511+
},
1512+
},
1513+
],
1514+
});
1515+
1516+
expect(await executor.readCommand()).toStrictEqual({
1517+
id: 2,
1518+
jsonrpc: '2.0',
1519+
result: {
1520+
historicalPrice: {
1521+
intervals: {
1522+
P1D: [[1635724800000, '1']],
1523+
},
1524+
updateTime: 1635724800000,
1525+
},
1526+
},
1527+
});
1528+
});
1529+
14761530
it('supports `onAssetsLookup` export', async () => {
14771531
const CODE = `
14781532
module.exports.onAssetsLookup = () => ({ assets: {} });

packages/snaps-execution-environments/src/common/commands.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ describe('getHandlerArguments', () => {
4141
).toThrow('Invalid request params');
4242
});
4343

44+
it('validates the request params for the OnAssetHistoricalPrice handler', () => {
45+
expect(() =>
46+
getHandlerArguments(MOCK_ORIGIN, HandlerType.OnAssetHistoricalPrice, {
47+
id: 1,
48+
jsonrpc: '2.0',
49+
method: 'foo',
50+
params: {},
51+
}),
52+
).toThrow('Invalid request params');
53+
});
54+
4455
it('throws for invalid handler types', () => {
4556
expect(() =>
4657
// @ts-expect-error Invalid handler type.

packages/snaps-execution-environments/src/common/commands.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
assertIsOnAssetsLookupRequestArguments,
1919
assertIsOnAssetsConversionRequestArguments,
2020
assertIsOnProtocolRequestArguments,
21+
assertIsOnAssetHistoricalPriceRequestArguments,
2122
} from './validation';
2223

2324
export type CommandMethodsMapping = {
@@ -59,6 +60,13 @@ export function getHandlerArguments(
5960
const { signature, signatureOrigin } = request.params;
6061
return { signature, signatureOrigin };
6162
}
63+
64+
case HandlerType.OnAssetHistoricalPrice: {
65+
assertIsOnAssetHistoricalPriceRequestArguments(request.params);
66+
const { from, to } = request.params;
67+
return { from, to };
68+
}
69+
6270
case HandlerType.OnAssetsLookup: {
6371
assertIsOnAssetsLookupRequestArguments(request.params);
6472
const { assets } = request.params;

packages/snaps-execution-environments/src/common/validation.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { UserInputEventType } from '@metamask/snaps-sdk';
22

33
import {
4+
assertIsOnAssetHistoricalPriceRequestArguments,
45
assertIsOnAssetsConversionRequestArguments,
56
assertIsOnAssetsLookupRequestArguments,
67
assertIsOnNameLookupRequestArguments,
@@ -378,3 +379,48 @@ describe('assertIsOnProtocolRequestArguments', () => {
378379
},
379380
);
380381
});
382+
383+
describe('assertIsOnAssetHistoricalPriceRequestArguments', () => {
384+
it.each([
385+
{
386+
from: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501',
387+
to: 'swift:0/iso4217:USD',
388+
},
389+
])(
390+
'does not throw for a valid asset historical price request object',
391+
(args) => {
392+
expect(() =>
393+
assertIsOnAssetHistoricalPriceRequestArguments(args),
394+
).not.toThrow();
395+
},
396+
);
397+
398+
it.each([
399+
true,
400+
false,
401+
null,
402+
undefined,
403+
0,
404+
1,
405+
'',
406+
'foo',
407+
[],
408+
{},
409+
{ from: [], to: 'swift:0/iso4217:USD' },
410+
{ to: 'swift:0/iso4217:USD', from: 'foo' },
411+
{ from: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501' },
412+
{ to: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501' },
413+
{
414+
from: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501',
415+
to: 'swift:0/iso4217:USD',
416+
foo: 'bar',
417+
},
418+
])(
419+
'throws if the value is not a valid asset historical price request object',
420+
(value) => {
421+
expect(() =>
422+
assertIsOnAssetHistoricalPriceRequestArguments(value as any),
423+
).toThrow('Invalid request params:');
424+
},
425+
);
426+
});

0 commit comments

Comments
 (0)