-
Notifications
You must be signed in to change notification settings - Fork 557
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new
snap_getCurrencyRate
RPC method (#2763)
This PR adds a new permitted RPC method called `snap_getCurrencyRate`. It takes a cryptocurrency symbol under the `currency` param. (We currently only support `btc`) and return a rate object if the rate is available. Fixes: #2762
- Loading branch information
1 parent
63ea955
commit d4e1403
Showing
13 changed files
with
353 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
packages/snaps-rpc-methods/src/permitted/getCurrencyRate.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import { JsonRpcEngine } from '@metamask/json-rpc-engine'; | ||
import { type GetCurrencyRateResult } from '@metamask/snaps-sdk'; | ||
import type { JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils'; | ||
|
||
import type { GetCurrencyRateParameters } from './getCurrencyRate'; | ||
import { getCurrencyRateHandler } from './getCurrencyRate'; | ||
|
||
describe('snap_getCurrencyRate', () => { | ||
describe('getCurrencyRateHandler', () => { | ||
it('has the expected shape', () => { | ||
expect(getCurrencyRateHandler).toMatchObject({ | ||
methodNames: ['snap_getCurrencyRate'], | ||
implementation: expect.any(Function), | ||
hookNames: { | ||
getCurrencyRate: true, | ||
}, | ||
}); | ||
}); | ||
}); | ||
|
||
describe('implementation', () => { | ||
it('returns the result from the `getCurrencyRate` hook', async () => { | ||
const { implementation } = getCurrencyRateHandler; | ||
|
||
const getCurrencyRate = jest.fn().mockReturnValue({ | ||
currency: 'usd', | ||
conversionRate: 1, | ||
conversionDate: 1, | ||
usdConversionRate: 1, | ||
}); | ||
|
||
const hooks = { | ||
getCurrencyRate, | ||
}; | ||
|
||
const engine = new JsonRpcEngine(); | ||
|
||
engine.push((request, response, next, end) => { | ||
const result = implementation( | ||
request as JsonRpcRequest<GetCurrencyRateParameters>, | ||
response as PendingJsonRpcResponse<GetCurrencyRateResult>, | ||
next, | ||
end, | ||
hooks, | ||
); | ||
|
||
result?.catch(end); | ||
}); | ||
|
||
const response = await engine.handle({ | ||
jsonrpc: '2.0', | ||
id: 1, | ||
method: 'snap_getCurrencyRate', | ||
params: { | ||
currency: 'btc', | ||
}, | ||
}); | ||
|
||
expect(response).toStrictEqual({ | ||
jsonrpc: '2.0', | ||
id: 1, | ||
result: { | ||
currency: 'usd', | ||
conversionRate: 1, | ||
conversionDate: 1, | ||
usdConversionRate: 1, | ||
}, | ||
}); | ||
}); | ||
|
||
it('returns null if there is no rate available', async () => { | ||
const { implementation } = getCurrencyRateHandler; | ||
|
||
const getCurrencyRate = jest.fn().mockReturnValue(undefined); | ||
|
||
const hooks = { | ||
getCurrencyRate, | ||
}; | ||
|
||
const engine = new JsonRpcEngine(); | ||
|
||
engine.push((request, response, next, end) => { | ||
const result = implementation( | ||
request as JsonRpcRequest<GetCurrencyRateParameters>, | ||
response as PendingJsonRpcResponse<GetCurrencyRateResult>, | ||
next, | ||
end, | ||
hooks, | ||
); | ||
|
||
result?.catch(end); | ||
}); | ||
|
||
const response = await engine.handle({ | ||
jsonrpc: '2.0', | ||
id: 1, | ||
method: 'snap_getCurrencyRate', | ||
params: { | ||
currency: 'btc', | ||
}, | ||
}); | ||
|
||
expect(response).toStrictEqual({ | ||
jsonrpc: '2.0', | ||
id: 1, | ||
result: null, | ||
}); | ||
}); | ||
|
||
it('throws on invalid params', async () => { | ||
const { implementation } = getCurrencyRateHandler; | ||
|
||
const getCurrencyRate = jest.fn().mockReturnValue({ | ||
currency: 'usd', | ||
conversionRate: 1, | ||
conversionDate: 1, | ||
usdConversionRate: 1, | ||
}); | ||
|
||
const hooks = { | ||
getCurrencyRate, | ||
}; | ||
|
||
const engine = new JsonRpcEngine(); | ||
|
||
engine.push((request, response, next, end) => { | ||
const result = implementation( | ||
request as JsonRpcRequest<GetCurrencyRateParameters>, | ||
response as PendingJsonRpcResponse<GetCurrencyRateResult>, | ||
next, | ||
end, | ||
hooks, | ||
); | ||
|
||
result?.catch(end); | ||
}); | ||
|
||
const response = await engine.handle({ | ||
jsonrpc: '2.0', | ||
id: 1, | ||
method: 'snap_getCurrencyRate', | ||
params: { | ||
currency: 'eth', | ||
}, | ||
}); | ||
|
||
expect(response).toStrictEqual({ | ||
error: { | ||
code: -32602, | ||
message: | ||
'Invalid params: At path: currency -- Expected the value to satisfy a union of `literal`, but received: "eth".', | ||
stack: expect.any(String), | ||
}, | ||
id: 1, | ||
jsonrpc: '2.0', | ||
}); | ||
}); | ||
}); | ||
}); |
102 changes: 102 additions & 0 deletions
102
packages/snaps-rpc-methods/src/permitted/getCurrencyRate.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine'; | ||
import type { PermittedHandlerExport } from '@metamask/permission-controller'; | ||
import { rpcErrors } from '@metamask/rpc-errors'; | ||
import type { | ||
AvailableCurrency, | ||
CurrencyRate, | ||
GetCurrencyRateParams, | ||
GetCurrencyRateResult, | ||
JsonRpcRequest, | ||
} from '@metamask/snaps-sdk'; | ||
import { currency, type InferMatching } from '@metamask/snaps-utils'; | ||
import { StructError, create, object, union } from '@metamask/superstruct'; | ||
import type { PendingJsonRpcResponse } from '@metamask/utils'; | ||
|
||
import type { MethodHooksObject } from '../utils'; | ||
|
||
const hookNames: MethodHooksObject<GetCurrencyRateMethodHooks> = { | ||
getCurrencyRate: true, | ||
}; | ||
|
||
export type GetCurrencyRateMethodHooks = { | ||
/** | ||
* @param currency - The currency symbol. | ||
* Currently only 'btc' is supported. | ||
* @returns The {@link CurrencyRate} object. | ||
*/ | ||
getCurrencyRate: (currency: AvailableCurrency) => CurrencyRate | undefined; | ||
}; | ||
|
||
export const getCurrencyRateHandler: PermittedHandlerExport< | ||
GetCurrencyRateMethodHooks, | ||
GetCurrencyRateParameters, | ||
GetCurrencyRateResult | ||
> = { | ||
methodNames: ['snap_getCurrencyRate'], | ||
implementation: getGetCurrencyRateImplementation, | ||
hookNames, | ||
}; | ||
|
||
const GetCurrencyRateParametersStruct = object({ | ||
currency: union([currency('btc')]), | ||
}); | ||
|
||
export type GetCurrencyRateParameters = InferMatching< | ||
typeof GetCurrencyRateParametersStruct, | ||
GetCurrencyRateParams | ||
>; | ||
|
||
/** | ||
* The `snap_getCurrencyRate` method implementation. | ||
* | ||
* @param req - The JSON-RPC request object. | ||
* @param res - The JSON-RPC response object. | ||
* @param _next - The `json-rpc-engine` "next" callback. Not used by this | ||
* function. | ||
* @param end - The `json-rpc-engine` "end" callback. | ||
* @param hooks - The RPC method hooks. | ||
* @param hooks.getCurrencyRate - The function to get the rate. | ||
* @returns Nothing. | ||
*/ | ||
function getGetCurrencyRateImplementation( | ||
req: JsonRpcRequest<GetCurrencyRateParameters>, | ||
res: PendingJsonRpcResponse<GetCurrencyRateResult>, | ||
_next: unknown, | ||
end: JsonRpcEngineEndCallback, | ||
{ getCurrencyRate }: GetCurrencyRateMethodHooks, | ||
): void { | ||
const { params } = req; | ||
|
||
try { | ||
const validatedParams = getValidatedParams(params); | ||
|
||
const { currency: selectedCurrency } = validatedParams; | ||
|
||
res.result = getCurrencyRate(selectedCurrency) ?? null; | ||
} catch (error) { | ||
return end(error); | ||
} | ||
|
||
return end(); | ||
} | ||
|
||
/** | ||
* Validate the getCurrencyRate method `params` and returns them cast to the correct | ||
* type. Throws if validation fails. | ||
* | ||
* @param params - The unvalidated params object from the method request. | ||
* @returns The validated getCurrencyRate method parameter object. | ||
*/ | ||
function getValidatedParams(params: unknown): GetCurrencyRateParameters { | ||
try { | ||
return create(params, GetCurrencyRateParametersStruct); | ||
} catch (error) { | ||
if (error instanceof StructError) { | ||
throw rpcErrors.invalidParams({ | ||
message: `Invalid params: ${error.message}.`, | ||
}); | ||
} | ||
/* istanbul ignore next */ | ||
throw rpcErrors.internal(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
export type Currency<Value extends string> = | ||
| Lowercase<Value> | ||
| Uppercase<Value>; | ||
|
||
export type AvailableCurrency = Currency<'btc'>; | ||
|
||
/** | ||
* The currency rate object. | ||
* | ||
* @property currency - The native currency symbol used for the conversion (e.g 'usd'). | ||
* @property conversionRate - The conversion rate from the cryptocurrency to the native currency. | ||
* @property conversionDate - The date of the conversion rate as a UNIX timestamp. | ||
* @property usdConversionRate - The conversion rate to USD. | ||
*/ | ||
export type CurrencyRate = { | ||
currency: string; | ||
conversionRate: number; | ||
conversionDate: number; | ||
usdConversionRate?: number; | ||
}; | ||
|
||
/** | ||
* The request parameters for the `snap_getCurrencyRate` method. | ||
* | ||
* @property currency - The currency symbol. | ||
*/ | ||
export type GetCurrencyRateParams = { | ||
currency: AvailableCurrency; | ||
}; | ||
|
||
/** | ||
* The result returned by the `snap_getCurrencyRate` method, which is the {@link CurrencyRate} object. | ||
*/ | ||
export type GetCurrencyRateResult = CurrencyRate | null; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"branches": 99.74, | ||
"functions": 98.9, | ||
"functions": 98.91, | ||
"lines": 99.45, | ||
"statements": 96.29 | ||
"statements": 96.3 | ||
} |
Oops, something went wrong.