Skip to content

Commit 9a467b6

Browse files
authored
feat: Add market data to onAssetsConversion handler (#3299)
This adds the `includeMarketData` param and `marketData` field to `onAssetsConversion` handler.
1 parent 785a382 commit 9a467b6

File tree

14 files changed

+444
-53
lines changed

14 files changed

+444
-53
lines changed

packages/examples/packages/browserify-plugin/snap.manifest.json

+1-1
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": "Qbr3VIystF/tEKyXESJ3ugB8m7n+E0yH8FGCx8od0lA=",
10+
"shasum": "ipeiQKWLZEg5Snm73jZkQ7VeusFnAMPd4ws5zWo7/NI=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/browserify/snap.manifest.json

+1-1
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": "KiWycLXvfEReGjt8jp3UpL4OZj1o5sDKMO6IXtwOZIQ=",
10+
"shasum": "uPtVkZpOxZzVcTSWX40MuXbED38DLAf9hNiNAR88+10=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

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

+100
Original file line numberDiff line numberDiff line change
@@ -4431,6 +4431,106 @@ describe('SnapController', () => {
44314431

44324432
snapController.destroy();
44334433
});
4434+
4435+
it('returns the value when `onAssetsConversion` returns a valid response with market data', async () => {
4436+
const rootMessenger = getControllerMessenger();
4437+
const messenger = getSnapControllerMessenger(rootMessenger);
4438+
const snapController = getSnapController(
4439+
getSnapControllerOptions({
4440+
messenger,
4441+
state: {
4442+
snaps: getPersistedSnapsState(),
4443+
},
4444+
}),
4445+
);
4446+
4447+
rootMessenger.registerActionHandler(
4448+
'PermissionController:getPermissions',
4449+
() => ({
4450+
[SnapEndowments.Assets]: {
4451+
caveats: [
4452+
{
4453+
type: SnapCaveatType.ChainIds,
4454+
value: ['bip122:000000000019d6689c085ae165831e93'],
4455+
},
4456+
],
4457+
date: 1664187844588,
4458+
id: 'izn0WGUO8cvq_jqvLQuQP',
4459+
invoker: MOCK_SNAP_ID,
4460+
parentCapability: SnapEndowments.Assets,
4461+
},
4462+
}),
4463+
);
4464+
4465+
rootMessenger.registerActionHandler(
4466+
'SubjectMetadataController:getSubjectMetadata',
4467+
() => MOCK_SNAP_SUBJECT_METADATA,
4468+
);
4469+
4470+
rootMessenger.registerActionHandler(
4471+
'ExecutionService:handleRpcRequest',
4472+
async () =>
4473+
Promise.resolve({
4474+
conversionRates: {
4475+
'bip122:000000000019d6689c085ae165831e93/slip44:0': {
4476+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': {
4477+
rate: '400',
4478+
conversionTime: 1737548790,
4479+
marketData: {
4480+
marketCap: '123',
4481+
totalVolume: '123',
4482+
circulatingSupply: '123',
4483+
allTimeHigh: '123',
4484+
allTimeLow: '123',
4485+
pricePercentChange: { all: 1.23 },
4486+
},
4487+
},
4488+
},
4489+
},
4490+
}),
4491+
);
4492+
4493+
expect(
4494+
await snapController.handleRequest({
4495+
snapId: MOCK_SNAP_ID,
4496+
origin: MOCK_ORIGIN,
4497+
handler: HandlerType.OnAssetsConversion,
4498+
request: {
4499+
jsonrpc: '2.0',
4500+
method: ' ',
4501+
params: {
4502+
conversions: [
4503+
{
4504+
from: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
4505+
to: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501',
4506+
},
4507+
],
4508+
includeMarketData: true,
4509+
},
4510+
id: 1,
4511+
},
4512+
}),
4513+
).toStrictEqual({
4514+
conversionRates: {
4515+
'bip122:000000000019d6689c085ae165831e93/slip44:0': {
4516+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': {
4517+
rate: '400',
4518+
conversionTime: 1737548790,
4519+
marketData: {
4520+
marketCap: '123',
4521+
totalVolume: '123',
4522+
circulatingSupply: '123',
4523+
allTimeHigh: '123',
4524+
allTimeLow: '123',
4525+
pricePercentChange: { all: 1.23 },
4526+
},
4527+
},
4528+
},
4529+
},
4530+
});
4531+
4532+
snapController.destroy();
4533+
});
44344534
});
44354535

44364536
describe('onAssetHistoricalPrice', () => {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ import type {
5959
import {
6060
AuxiliaryFileEncoding,
6161
getErrorMessage,
62-
OnAssetsConversionResponseStruct,
6362
OnAssetsLookupResponseStruct,
6463
} from '@metamask/snaps-sdk';
6564
import type {
@@ -105,6 +104,7 @@ import {
105104
OnSettingsPageResponseStruct,
106105
isValidUrl,
107106
OnAssetHistoricalPriceResponseStruct,
107+
OnAssetsConversionResponseStruct,
108108
} from '@metamask/snaps-utils';
109109
import type {
110110
Json,

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

+45
Original file line numberDiff line numberDiff line change
@@ -1608,6 +1608,51 @@ describe('BaseSnapExecutor', () => {
16081608
});
16091609
});
16101610

1611+
it('supports `onAssetsConversion` export with the market data flag', async () => {
1612+
const CODE = `
1613+
module.exports.onAssetsConversion = () => ({ conversionRates: {} });
1614+
`;
1615+
1616+
const executor = new TestSnapExecutor();
1617+
await executor.executeSnap(1, MOCK_SNAP_ID, CODE, []);
1618+
1619+
expect(await executor.readCommand()).toStrictEqual({
1620+
jsonrpc: '2.0',
1621+
id: 1,
1622+
result: 'OK',
1623+
});
1624+
1625+
await executor.writeCommand({
1626+
jsonrpc: '2.0',
1627+
id: 2,
1628+
method: 'snapRpc',
1629+
params: [
1630+
MOCK_SNAP_ID,
1631+
HandlerType.OnAssetsConversion,
1632+
MOCK_ORIGIN,
1633+
{
1634+
jsonrpc: '2.0',
1635+
method: '',
1636+
params: {
1637+
conversions: [
1638+
{
1639+
from: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
1640+
to: 'eip155:1/slip44:60',
1641+
},
1642+
],
1643+
includeMarketData: true,
1644+
},
1645+
},
1646+
],
1647+
});
1648+
1649+
expect(await executor.readCommand()).toStrictEqual({
1650+
id: 2,
1651+
jsonrpc: '2.0',
1652+
result: { conversionRates: {} },
1653+
});
1654+
});
1655+
16111656
it('supports onSignature export', async () => {
16121657
const CODE = `
16131658
module.exports.onSignature = ({ signature, signatureOrigin }) =>

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

+20
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,24 @@ describe('assertIsOnAssetsConversionRequestArguments', () => {
283283
},
284284
],
285285
},
286+
{
287+
conversions: [
288+
{
289+
from: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501',
290+
to: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
291+
},
292+
],
293+
includeMarketData: true,
294+
},
295+
{
296+
conversions: [
297+
{
298+
from: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501',
299+
to: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
300+
},
301+
],
302+
includeMarketData: false,
303+
},
286304
])('does not throw for a valid assets conversion param object', (value) => {
287305
expect(() =>
288306
assertIsOnAssetsConversionRequestArguments(value),
@@ -305,6 +323,8 @@ describe('assertIsOnAssetsConversionRequestArguments', () => {
305323
{ conversions: [{}] },
306324
{ conversions: [{ from: 'foo' }] },
307325
{ conversions: [{ from: 'foo', to: 'foo' }] },
326+
{ includeMarketData: true },
327+
{ includeMarketData: false },
308328
])(
309329
'throws if the value is not a valid assets conversion params object',
310330
(value) => {

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

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
any,
1010
array,
1111
assign,
12+
boolean,
1213
enums,
1314
is,
1415
literal,
@@ -285,6 +286,7 @@ export const OnAssetsConversionRequestArgumentsStruct = object({
285286
1,
286287
Infinity,
287288
),
289+
includeMarketData: optional(boolean()),
288290
});
289291

290292
export type OnAssetsConversionRequestArguments = Infer<

packages/snaps-sdk/src/types/handlers/assets-conversion.test.ts

-24
This file was deleted.

packages/snaps-sdk/src/types/handlers/assets-conversion.ts

+46-23
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,59 @@
1-
import type { Infer } from '@metamask/superstruct';
2-
import {
3-
number,
4-
object,
5-
string,
6-
optional,
7-
record,
8-
nullable,
9-
} from '@metamask/superstruct';
10-
import { CaipAssetTypeStruct, type CaipAssetType } from '@metamask/utils';
1+
import { type CaipAssetType } from '@metamask/utils';
112

12-
export const AssetConversionStruct = object({
13-
rate: string(),
14-
conversionTime: number(),
15-
expirationTime: optional(number()),
16-
});
17-
18-
export const OnAssetsConversionResponseStruct = object({
19-
conversionRates: record(
20-
CaipAssetTypeStruct,
21-
record(CaipAssetTypeStruct, nullable(AssetConversionStruct)),
22-
),
23-
});
3+
/**
4+
* The market data for an asset.
5+
*
6+
* @property marketCap - The market capitalization of the asset.
7+
* @property totalVolume - The total volume of the asset.
8+
* @property circulatingSupply - The circulating supply of the asset.
9+
* @property allTimeHigh - The all-time high price of the asset.
10+
* @property allTimeLow - The all-time low price of the asset.
11+
* @property pricePercentChange - The percentage change in price over different intervals.
12+
* @property pricePercentChange.interval - The time interval for the price change as a ISO 8601 duration
13+
* or the string "all" to represent the all-time change.
14+
*/
15+
export type MarketData = {
16+
marketCap: string;
17+
totalVolume: string;
18+
circulatingSupply: string;
19+
allTimeHigh: string;
20+
allTimeLow: string;
21+
pricePercentChange: {
22+
[interval: string]: number;
23+
};
24+
};
2425

25-
export type AssetConversion = Infer<typeof AssetConversionStruct>;
26+
/**
27+
* The conversion rate between two assets.
28+
*
29+
* @property rate - The conversion rate between the two assets.
30+
* @property marketData - The market data for the asset, if requested.
31+
* @property conversionTime - The time at which the conversion rate was calculated.
32+
* @property expirationTime - The time at which the conversion rate expires.
33+
*/
34+
export type AssetConversion = {
35+
rate: string;
36+
marketData?: MarketData;
37+
conversionTime: number;
38+
expirationTime?: number;
39+
};
2640

41+
/**
42+
* The arguments for the `onAssetsConversion` handler.
43+
*
44+
* @property conversions - An array of objects containing the `from` and `to` asset types.
45+
* @property includeMarketData - Whether to include market data in the response.
46+
*/
2747
export type OnAssetsConversionArguments = {
2848
conversions: { from: CaipAssetType; to: CaipAssetType }[];
49+
includeMarketData?: boolean;
2950
};
3051

3152
/**
3253
* The `onAssetsConversion` handler. This is called by MetaMask when querying about asset conversion on specific chains.
3354
*
55+
* @param args - The arguments for the handler.
56+
* see {@link OnAssetsConversionArguments}.
3457
* @returns The conversion for each asset. See
3558
* {@link OnAssetsConversionResponse}.
3659
*/

packages/snaps-sdk/src/types/handlers/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export type * from './asset-historical-price';
2-
export * from './assets-conversion';
2+
export type * from './assets-conversion';
33
export * from './assets-lookup';
44
export type * from './cronjob';
55
export type * from './home-page';

packages/snaps-utils/coverage.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"branches": 99.75,
33
"functions": 98.95,
4-
"lines": 98.56,
5-
"statements": 97.05
4+
"lines": 98.57,
5+
"statements": 97.07
66
}

0 commit comments

Comments
 (0)