diff --git a/.eslintrc.js b/.eslintrc.js index 878c42ca..94c45af6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,6 +8,7 @@ module.exports = { files: ['*.ts'], extends: ['@metamask/eslint-config-typescript'], rules: { + '@typescript-eslint/prefer-nullish-coalescing': 'off', // TODO: resolve warnings and remove to make into errors '@typescript-eslint/consistent-type-definitions': 'off', '@typescript-eslint/naming-convention': 'off', diff --git a/src/block-cache.test.ts b/src/block-cache.test.ts index 4a67cc28..3073161f 100644 --- a/src/block-cache.test.ts +++ b/src/block-cache.test.ts @@ -1,6 +1,6 @@ import { providerFromEngine } from '@metamask/eth-json-rpc-provider'; +import { JsonRpcEngine } from '@metamask/json-rpc-engine'; import { PollingBlockTracker } from 'eth-block-tracker'; -import { JsonRpcEngine } from 'json-rpc-engine'; import pify from 'pify'; import { createBlockCacheMiddleware } from '.'; diff --git a/src/block-cache.ts b/src/block-cache.ts index 3b05d540..5bfd7e47 100644 --- a/src/block-cache.ts +++ b/src/block-cache.ts @@ -1,6 +1,6 @@ +import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; +import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; import type { PollingBlockTracker } from 'eth-block-tracker'; -import type { JsonRpcRequest } from 'json-rpc-engine'; -import { createAsyncMiddleware } from 'json-rpc-engine'; import { projectLogger, createModuleLogger } from './logging-utils'; import type { @@ -51,7 +51,7 @@ class BlockCacheStrategy { } async get( - request: JsonRpcRequest, + request: JsonRpcRequest, requestedBlockNumber: string, ): Promise { // lookup block cache @@ -62,7 +62,7 @@ class BlockCacheStrategy { } async set( - request: JsonRpcRequest, + request: JsonRpcRequest, requestedBlockNumber: string, result: Block, ): Promise { @@ -81,7 +81,7 @@ class BlockCacheStrategy { blockCache[identifier] = result; } - canCacheRequest(request: JsonRpcRequest): boolean { + canCacheRequest(request: JsonRpcRequest): boolean { // check request method if (!canCache(request.method)) { return false; @@ -96,7 +96,7 @@ class BlockCacheStrategy { return true; } - canCacheResult(request: JsonRpcRequest, result: Block): boolean { + canCacheResult(request: JsonRpcRequest, result: Block): boolean { // never cache empty values (e.g. undefined) if (emptyValues.includes(result as any)) { return false; @@ -135,7 +135,10 @@ class BlockCacheStrategy { export function createBlockCacheMiddleware({ blockTracker, -}: BlockCacheMiddlewareOptions = {}): JsonRpcCacheMiddleware { +}: BlockCacheMiddlewareOptions = {}): JsonRpcCacheMiddleware< + JsonRpcParams, + Json +> { // validate options if (!blockTracker) { throw new Error( @@ -153,7 +156,7 @@ export function createBlockCacheMiddleware({ }; return createAsyncMiddleware( - async (req: JsonRpcRequestToCache, res, next) => { + async (req: JsonRpcRequestToCache, res, next) => { // allow cach to be skipped if so specified if (req.skipCache) { return next(); diff --git a/src/block-ref-rewrite.ts b/src/block-ref-rewrite.ts index 91f6b555..cdc993c8 100644 --- a/src/block-ref-rewrite.ts +++ b/src/block-ref-rewrite.ts @@ -1,6 +1,7 @@ +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; +import type { Json, JsonRpcParams } from '@metamask/utils'; import type { PollingBlockTracker } from 'eth-block-tracker'; -import type { JsonRpcMiddleware } from 'json-rpc-engine'; -import { createAsyncMiddleware } from 'json-rpc-engine'; import { blockTagParamIndex } from './utils/cache'; @@ -10,7 +11,10 @@ interface BlockRefRewriteMiddlewareOptions { export function createBlockRefRewriteMiddleware({ blockTracker, -}: BlockRefRewriteMiddlewareOptions = {}): JsonRpcMiddleware { +}: BlockRefRewriteMiddlewareOptions = {}): JsonRpcMiddleware< + JsonRpcParams, + Json +> { if (!blockTracker) { throw Error( 'BlockRefRewriteMiddleware - mandatory "blockTracker" option is missing.', @@ -24,13 +28,11 @@ export function createBlockRefRewriteMiddleware({ return next(); } // skip if not "latest" - let blockRef: string | undefined = Array.isArray(req.params) - ? req.params[blockRefIndex] - : undefined; - // omitted blockRef implies "latest" - if (blockRef === undefined) { - blockRef = 'latest'; - } + const blockRef: string | undefined = + Array.isArray(req.params) && req.params[blockRefIndex] + ? (req.params[blockRefIndex] as string) + : // omitted blockRef implies "latest" + 'latest'; if (blockRef !== 'latest') { return next(); diff --git a/src/block-ref.test.ts b/src/block-ref.test.ts index 38e1fe5d..89f8f7ce 100644 --- a/src/block-ref.test.ts +++ b/src/block-ref.test.ts @@ -1,8 +1,8 @@ import { providerFromEngine } from '@metamask/eth-json-rpc-provider'; import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import { JsonRpcEngine } from '@metamask/json-rpc-engine'; import { PollingBlockTracker } from 'eth-block-tracker'; -import type { JsonRpcMiddleware } from 'json-rpc-engine'; -import { JsonRpcEngine } from 'json-rpc-engine'; import { createBlockRefMiddleware } from '.'; import { diff --git a/src/block-ref.ts b/src/block-ref.ts index ff65ba44..c4627fee 100644 --- a/src/block-ref.ts +++ b/src/block-ref.ts @@ -1,11 +1,13 @@ import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; -import clone from 'clone'; -import type { PollingBlockTracker } from 'eth-block-tracker'; +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; import type { - JsonRpcMiddleware, + Json, + JsonRpcParams, PendingJsonRpcResponse, -} from 'json-rpc-engine'; -import { createAsyncMiddleware } from 'json-rpc-engine'; +} from '@metamask/utils'; +import clone from 'clone'; +import type { PollingBlockTracker } from 'eth-block-tracker'; import pify from 'pify'; import { projectLogger, createModuleLogger } from './logging-utils'; @@ -22,7 +24,7 @@ const log = createModuleLogger(projectLogger, 'block-ref'); export function createBlockRefMiddleware({ provider, blockTracker, -}: BlockRefMiddlewareOptions = {}): JsonRpcMiddleware { +}: BlockRefMiddlewareOptions = {}): JsonRpcMiddleware { if (!provider) { throw Error('BlockRefMiddleware - mandatory "provider" option is missing.'); } diff --git a/src/block-tracker-inspector.ts b/src/block-tracker-inspector.ts index da9fc1e1..08795c93 100644 --- a/src/block-tracker-inspector.ts +++ b/src/block-tracker-inspector.ts @@ -1,9 +1,7 @@ +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; +import type { JsonRpcParams, PendingJsonRpcResponse } from '@metamask/utils'; import type { PollingBlockTracker } from 'eth-block-tracker'; -import type { - JsonRpcMiddleware, - PendingJsonRpcResponse, -} from 'json-rpc-engine'; -import { createAsyncMiddleware } from 'json-rpc-engine'; import { projectLogger, createModuleLogger } from './logging-utils'; @@ -37,7 +35,7 @@ function hasProperty( } function getResultBlockNumber( - response: PendingJsonRpcResponse, + response: PendingJsonRpcResponse, ): string | undefined { const { result } = response; if ( @@ -58,8 +56,8 @@ function getResultBlockNumber( export function createBlockTrackerInspectorMiddleware({ blockTracker, }: BlockTrackerInspectorMiddlewareOptions): JsonRpcMiddleware< - unknown, - unknown + JsonRpcParams, + JsonRpcParams > { return createAsyncMiddleware(async (req, res, next) => { if (!futureBlockRefRequests.includes(req.method)) { diff --git a/src/fetch.ts b/src/fetch.ts index 340d4fff..3a44559e 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -1,7 +1,8 @@ -import type { EthereumRpcError } from 'eth-rpc-errors'; -import { ethErrors } from 'eth-rpc-errors'; -import type { JsonRpcMiddleware, JsonRpcRequest } from 'json-rpc-engine'; -import { createAsyncMiddleware } from 'json-rpc-engine'; +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; +import type { JsonRpcError, DataWithOptionalCause } from '@metamask/rpc-errors'; +import { rpcErrors } from '@metamask/rpc-errors'; +import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; import type { Block } from './types'; import { timeout } from './utils/timeout'; @@ -17,7 +18,7 @@ const RETRIABLE_ERRORS: string[] = [ 'Failed to fetch', ]; -export interface PayloadWithOrigin extends JsonRpcRequest { +export interface PayloadWithOrigin extends JsonRpcRequest { origin?: string; } interface Request { @@ -53,7 +54,7 @@ export function createFetchMiddleware({ fetch: typeof global.fetch; rpcUrl: string; originHttpHeaderKey?: string; -}): JsonRpcMiddleware { +}): JsonRpcMiddleware { return createAsyncMiddleware(async (req, res, _next) => { const { fetchUrl, fetchParams } = createFetchConfigFromReq({ btoa, @@ -165,7 +166,7 @@ export function createFetchConfigFromReq({ // prepare payload // copy only canonical json rpc properties - const payload: JsonRpcRequest = { + const payload: JsonRpcRequest = { id: req.id, jsonrpc: req.jsonrpc, method: req.method, @@ -215,11 +216,11 @@ function normalizeUrlFromParsed(parsedUrl: URL): string { return result; } -function createRatelimitError(): EthereumRpcError { +function createRatelimitError(): JsonRpcError { return rpcErrors.internal({ message: `Request is being rate limited.` }); } -function createTimeoutError(): EthereumRpcError { +function createTimeoutError(): JsonRpcError { let msg = `Gateway timeout. The request took too long to process. `; msg += `This can happen when querying logs over too wide a block range.`; return rpcErrors.internal({ message: msg }); diff --git a/src/inflight-cache.test.ts b/src/inflight-cache.test.ts index a0946d42..c8abe24e 100644 --- a/src/inflight-cache.test.ts +++ b/src/inflight-cache.test.ts @@ -1,4 +1,4 @@ -import { JsonRpcEngine } from 'json-rpc-engine'; +import { JsonRpcEngine } from '@metamask/json-rpc-engine'; import pify from 'pify'; import { createInflightCacheMiddleware } from '.'; diff --git a/src/inflight-cache.ts b/src/inflight-cache.ts index 75dda270..d65fdbe5 100644 --- a/src/inflight-cache.ts +++ b/src/inflight-cache.ts @@ -1,12 +1,16 @@ +import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; +import type { + JsonRpcParams, + Json, + PendingJsonRpcResponse, +} from '@metamask/utils'; import clone from 'clone'; -import type { PendingJsonRpcResponse } from 'json-rpc-engine'; -import { createAsyncMiddleware } from 'json-rpc-engine'; import { projectLogger, createModuleLogger } from './logging-utils'; import type { JsonRpcRequestToCache, JsonRpcCacheMiddleware } from './types'; import { cacheIdentifierForRequest } from './utils/cache'; -type RequestHandlers = (handledRes: PendingJsonRpcResponse) => void; +type RequestHandlers = (handledRes: PendingJsonRpcResponse) => void; interface InflightRequest { [cacheId: string]: RequestHandlers[]; } @@ -14,13 +18,13 @@ interface InflightRequest { const log = createModuleLogger(projectLogger, 'inflight-cache'); export function createInflightCacheMiddleware(): JsonRpcCacheMiddleware< - unknown, - unknown + JsonRpcParams, + Json > { const inflightRequests: InflightRequest = {}; return createAsyncMiddleware( - async (req: JsonRpcRequestToCache, res, next) => { + async (req: JsonRpcRequestToCache, res, next) => { // allow cach to be skipped if so specified if (req.skipCache) { return next(); @@ -68,23 +72,21 @@ export function createInflightCacheMiddleware(): JsonRpcCacheMiddleware< ); async function createActiveRequestHandler( - res: PendingJsonRpcResponse, + res: PendingJsonRpcResponse, activeRequestHandlers: RequestHandlers[], ): Promise { const { resolve, promise } = deferredPromise(); - activeRequestHandlers.push( - (handledRes: PendingJsonRpcResponse) => { - // append a copy of the result and error to the response - res.result = clone(handledRes.result); - res.error = clone(handledRes.error); - resolve(); - }, - ); + activeRequestHandlers.push((handledRes: PendingJsonRpcResponse) => { + // append a copy of the result and error to the response + res.result = clone(handledRes.result); + res.error = clone(handledRes.error); + resolve(); + }); return promise; } function handleActiveRequest( - res: PendingJsonRpcResponse, + res: PendingJsonRpcResponse, activeRequestHandlers: RequestHandlers[], ): void { // use setTimeout so we can resolve our original request first diff --git a/src/providerAsMiddleware.ts b/src/providerAsMiddleware.ts index af1a8ec8..70f620aa 100644 --- a/src/providerAsMiddleware.ts +++ b/src/providerAsMiddleware.ts @@ -1,12 +1,14 @@ import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; import type { - JsonRpcMiddleware, + Json, + JsonRpcParams, PendingJsonRpcResponse, -} from 'json-rpc-engine'; +} from '@metamask/utils'; export function providerAsMiddleware( provider: SafeEventEmitterProvider, -): JsonRpcMiddleware { +): JsonRpcMiddleware { return (req, res, _next, end) => { // send request to provider provider.sendAsync( @@ -26,7 +28,7 @@ export function providerAsMiddleware( export function ethersProviderAsMiddleware( provider: SafeEventEmitterProvider, -): JsonRpcMiddleware { +): JsonRpcMiddleware { return (req, res, _next, end) => { // send request to provider provider.send( @@ -34,7 +36,7 @@ export function ethersProviderAsMiddleware( (err: unknown, providerRes: PendingJsonRpcResponse) => { // forward any error if (err) { - // TODO: Remove this cast when next major `json-rpc-engine` release is out + // TODO: Remove this cast when next major `@metamask/json-rpc-engine` release is out // The next release changes how errors are propogated. return end(err as Error); } diff --git a/src/retryOnEmpty.test.ts b/src/retryOnEmpty.test.ts index 554e46ba..644501d8 100644 --- a/src/retryOnEmpty.test.ts +++ b/src/retryOnEmpty.test.ts @@ -1,8 +1,11 @@ import { providerFromEngine } from '@metamask/eth-json-rpc-provider'; import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; +import type { + JsonRpcMiddleware, + JsonRpcRequest, +} from '@metamask/json-rpc-engine'; +import { JsonRpcEngine } from '@metamask/json-rpc-engine'; import { PollingBlockTracker } from 'eth-block-tracker'; -import type { JsonRpcMiddleware, JsonRpcRequest } from 'json-rpc-engine'; -import { JsonRpcEngine } from 'json-rpc-engine'; import { createRetryOnEmptyMiddleware } from '.'; import type { ProviderRequestStub } from '../test/util/helpers'; diff --git a/src/retryOnEmpty.ts b/src/retryOnEmpty.ts index 57542859..9bb4dfb2 100644 --- a/src/retryOnEmpty.ts +++ b/src/retryOnEmpty.ts @@ -1,11 +1,13 @@ import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; -import clone from 'clone'; -import type { PollingBlockTracker } from 'eth-block-tracker'; +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; import type { - JsonRpcMiddleware, + Json, + JsonRpcParams, PendingJsonRpcResponse, -} from 'json-rpc-engine'; -import { createAsyncMiddleware } from 'json-rpc-engine'; +} from '@metamask/utils'; +import clone from 'clone'; +import type { PollingBlockTracker } from 'eth-block-tracker'; import pify from 'pify'; import { projectLogger, createModuleLogger } from './logging-utils'; @@ -37,7 +39,7 @@ interface RetryOnEmptyMiddlewareOptions { export function createRetryOnEmptyMiddleware({ provider, blockTracker, -}: RetryOnEmptyMiddlewareOptions = {}): JsonRpcMiddleware { +}: RetryOnEmptyMiddlewareOptions = {}): JsonRpcMiddleware { if (!provider) { throw Error( 'RetryOnEmptyMiddleware - mandatory "provider" option is missing.', @@ -57,9 +59,10 @@ export function createRetryOnEmptyMiddleware({ return next(); } // skip if not exact block references - let blockRef: string | undefined = Array.isArray(req.params) - ? req.params[blockRefIndex] - : undefined; + let blockRef: string | undefined = + Array.isArray(req.params) && req.params[blockRefIndex] + ? (req.params[blockRefIndex] as string) + : undefined; // omitted blockRef implies "latest" if (blockRef === undefined) { blockRef = 'latest'; diff --git a/src/types.ts b/src/types.ts index b1302195..a8d9840d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,14 +1,19 @@ -import type { JsonRpcMiddleware, JsonRpcRequest } from 'json-rpc-engine'; +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; -export interface JsonRpcRequestToCache extends JsonRpcRequest { +export interface JsonRpcRequestToCache + extends JsonRpcRequest { skipCache?: boolean; } -export type JsonRpcCacheMiddleware = JsonRpcMiddleware extends ( - req: JsonRpcRequest, +export type JsonRpcCacheMiddleware< + Params extends JsonRpcParams, + Result extends Json, +> = JsonRpcMiddleware extends ( + req: JsonRpcRequest, ...args: infer X ) => infer Y - ? (req: JsonRpcRequestToCache, ...args: X) => Y + ? (req: JsonRpcRequestToCache, ...args: X) => Y : never; export type BlockData = string | string[]; diff --git a/src/utils/cache.ts b/src/utils/cache.ts index 7e98c4b3..a859ebbc 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -1,4 +1,4 @@ -import type { JsonRpcRequest } from 'json-rpc-engine'; +import type { Json, JsonRpcRequest } from '@metamask/utils'; import { configure } from 'safe-stable-stringify'; const stringify = configure({ bigint: false, circularValue: Error }); @@ -41,7 +41,7 @@ export enum CacheStrategy { * cached. */ export function cacheIdentifierForRequest( - request: JsonRpcRequest, + request: JsonRpcRequest, skipBlockRef?: boolean, ): string | null { const simpleParams = skipBlockRef @@ -69,7 +69,7 @@ export function canCache(method: string): boolean { * @param request - The JSON-RPC request. * @returns The block parameter in the given request, or `undefined` if none was found. */ -export function blockTagForRequest(request: JsonRpcRequest): unknown { +export function blockTagForRequest(request: JsonRpcRequest): Json | undefined { if (!request.params) { return undefined; } @@ -93,7 +93,7 @@ export function blockTagForRequest(request: JsonRpcRequest): unknown { * @param request - The JSON-RPC request. * @returns The request parameters with the block parameter removed, if one was found. */ -function paramsWithoutBlockTag(request: JsonRpcRequest): unknown { +function paramsWithoutBlockTag(request: JsonRpcRequest): Json { if (!request.params) { return []; } diff --git a/src/wallet.test.ts b/src/wallet.test.ts index 96c32f1a..7c69f872 100644 --- a/src/wallet.test.ts +++ b/src/wallet.test.ts @@ -1,5 +1,5 @@ import { providerFromEngine } from '@metamask/eth-json-rpc-provider'; -import { JsonRpcEngine } from 'json-rpc-engine'; +import { JsonRpcEngine } from '@metamask/json-rpc-engine'; import pify from 'pify'; import type { TransactionParams, MessageParams } from '.'; diff --git a/src/wallet.ts b/src/wallet.ts index 157f0560..396e9b35 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -1,72 +1,83 @@ import * as sigUtil from '@metamask/eth-sig-util'; -import { providerErrors, rpcErrors } from '@metamask/rpc-errors'; -import type { - JsonRpcMiddleware, +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import { + createAsyncMiddleware, + createScaffoldMiddleware, } from '@metamask/json-rpc-engine'; +import { providerErrors, rpcErrors } from '@metamask/rpc-errors'; import type { Json, JsonRpcParams, JsonRpcRequest, PendingJsonRpcResponse, } from '@metamask/utils'; -import { - createAsyncMiddleware, - createScaffoldMiddleware, -} from '@metamask/json-rpc-engine'; import type { Block } from './types'; -export interface TransactionParams { +/* +export type TransactionParams = { + [prop: string]: Json; from: string; } +*/ -export interface MessageParams extends TransactionParams { - data: string; +/* +export type TransactionParams = JsonRpcParams & { + from: string; } +*/ + +export type TransactionParams = { + from: string; +}; -export interface TypedMessageParams extends MessageParams { +export type MessageParams = TransactionParams & { + data: string; +}; + +export type TypedMessageParams = MessageParams & { version: string; -} +}; export interface WalletMiddlewareOptions { - getAccounts: (req: JsonRpcRequest) => Promise; + getAccounts: (req: JsonRpcRequest) => Promise; processDecryptMessage?: ( msgParams: MessageParams, - req: JsonRpcRequest, + req: JsonRpcRequest, ) => Promise; processEncryptionPublicKey?: ( address: string, - req: JsonRpcRequest, + req: JsonRpcRequest, ) => Promise; processEthSignMessage?: ( msgParams: MessageParams, - req: JsonRpcRequest, + req: JsonRpcRequest, ) => Promise; processPersonalMessage?: ( msgParams: MessageParams, - req: JsonRpcRequest, + req: JsonRpcRequest, ) => Promise; processTransaction?: ( txParams: TransactionParams, - req: JsonRpcRequest, + req: JsonRpcRequest, ) => Promise; processSignTransaction?: ( txParams: TransactionParams, - req: JsonRpcRequest, + req: JsonRpcRequest, ) => Promise; processTypedMessage?: ( msgParams: MessageParams, - req: JsonRpcRequest, + req: JsonRpcRequest, version: string, ) => Promise; processTypedMessageV3?: ( msgParams: TypedMessageParams, - req: JsonRpcRequest, + req: JsonRpcRequest, version: string, ) => Promise; processTypedMessageV4?: ( msgParams: TypedMessageParams, - req: JsonRpcRequest, + req: JsonRpcRequest, version: string, ) => Promise; } @@ -82,8 +93,8 @@ export function createWalletMiddleware({ processTypedMessage, processTypedMessageV3, processTypedMessageV4, -// }: WalletMiddlewareOptions): JsonRpcMiddleware { -}: WalletMiddlewareOptions): JsonRpcMiddleware { +}: // }: WalletMiddlewareOptions): JsonRpcMiddleware { +WalletMiddlewareOptions): JsonRpcMiddleware { if (!getAccounts) { throw new Error('opts.getAccounts is required'); } @@ -111,15 +122,15 @@ export function createWalletMiddleware({ // async function lookupAccounts( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { res.result = await getAccounts(req); } async function lookupDefaultAccount( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { const accounts = await getAccounts(req); res.result = accounts[0] || null; @@ -130,30 +141,46 @@ export function createWalletMiddleware({ // async function sendTransaction( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { if (!processTransaction) { throw rpcErrors.methodNotSupported(); } + if ( + !req.params || + !Array.isArray(req.params) || + !(req.params.length >= 1) + ) { + throw rpcErrors.invalidInput(); + } - const txParams: TransactionParams = - (req.params as TransactionParams[])[0] || {}; - txParams.from = await validateAndNormalizeKeyholder(txParams.from, req); + const params = req.params as [TransactionParams?]; + const txParams: TransactionParams = { + from: await validateAndNormalizeKeyholder(params[0]?.from || '', req), + }; res.result = await processTransaction(txParams, req); } async function signTransaction( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { if (!processSignTransaction) { throw rpcErrors.methodNotSupported(); } + if ( + !req.params || + !Array.isArray(req.params) || + !(req.params.length >= 1) + ) { + throw rpcErrors.invalidInput(); + } - const txParams: TransactionParams = - (req.params as TransactionParams[])[0] || {}; - txParams.from = await validateAndNormalizeKeyholder(txParams.from, req); + const params = req.params as [TransactionParams?]; + const txParams: TransactionParams = { + from: await validateAndNormalizeKeyholder(params[0]?.from || '', req), + }; res.result = await processSignTransaction(txParams, req); } @@ -162,20 +189,24 @@ export function createWalletMiddleware({ // async function ethSign( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { if (!processEthSignMessage) { throw rpcErrors.methodNotSupported(); } + if ( + !req?.params || + !Array.isArray(req.params) || + !(req.params.length >= 2) + ) { + throw rpcErrors.invalidInput(); + } - const address: string = await validateAndNormalizeKeyholder( - (req.params as string[])[0], - req, - ); - const message: string = (req.params as string[])[1]; - const extraParams: Record = - (req.params as Record[])[2] || {}; + const params = req.params as [string, string, Record?]; + const address: string = await validateAndNormalizeKeyholder(params[0], req); + const message = params[1]; + const extraParams = params[2] || {}; const msgParams: MessageParams = { ...extraParams, from: address, @@ -186,21 +217,25 @@ export function createWalletMiddleware({ } async function signTypedData( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { if (!processTypedMessage) { throw rpcErrors.methodNotSupported(); } + if ( + !req?.params || + !Array.isArray(req.params) || + !(req.params.length >= 2) + ) { + throw rpcErrors.invalidInput(); + } - const message: string = (req.params as string[])[0]; - const address: string = await validateAndNormalizeKeyholder( - (req.params as string[])[1], - req, - ); + const params = req.params as [string, string, Record?]; + const message = params[0]; + const address = await validateAndNormalizeKeyholder(params[1], req); const version = 'V1'; - const extraParams: Record = - (req.params as Record[])[2] || {}; + const extraParams = params[2] || {}; const msgParams: MessageParams = { ...extraParams, from: address, @@ -211,18 +246,24 @@ export function createWalletMiddleware({ } async function signTypedDataV3( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { if (!processTypedMessageV3) { throw rpcErrors.methodNotSupported(); } + if ( + !req?.params || + !Array.isArray(req.params) || + !(req.params.length >= 2) + ) { + throw rpcErrors.invalidInput(); + } - const address: string = await validateAndNormalizeKeyholder( - (req.params as string[])[0], - req, - ); - const message: string = (req.params as string[])[1]; + const params = req.params as [string, string]; + + const address = await validateAndNormalizeKeyholder(params[0], req); + const message = params[1]; const version = 'V3'; const msgParams: TypedMessageParams = { data: message, @@ -234,18 +275,24 @@ export function createWalletMiddleware({ } async function signTypedDataV4( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { if (!processTypedMessageV4) { throw rpcErrors.methodNotSupported(); } + if ( + !req?.params || + !Array.isArray(req.params) || + !(req.params.length >= 2) + ) { + throw rpcErrors.invalidInput(); + } - const address: string = await validateAndNormalizeKeyholder( - (req.params as string[])[0], - req, - ); - const message: string = (req.params as string[])[1]; + const params = req.params as [string, string]; + + const address = await validateAndNormalizeKeyholder(params[0], req); + const message = params[1]; const version = 'V4'; const msgParams: TypedMessageParams = { data: message, @@ -257,19 +304,27 @@ export function createWalletMiddleware({ } async function personalSign( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { if (!processPersonalMessage) { throw rpcErrors.methodNotSupported(); } + if ( + !req?.params || + !Array.isArray(req.params) || + !(req.params.length >= 2) + ) { + throw rpcErrors.invalidInput(); + } + + const params = req.params as [string, string, TransactionParams?]; // process normally - const firstParam: string = (req.params as string[])[0]; - const secondParam: string = (req.params as string[])[1]; + const firstParam = params[0]; + const secondParam = params[1]; // non-standard "extraParams" to be appended to our "msgParams" obj - const extraParams: Record = - (req.params as Record[])[2] || {}; + const extraParams = params[2] || {}; // We initially incorrectly ordered these parameters. // To gracefully respect users who adopted this API early, @@ -305,12 +360,21 @@ export function createWalletMiddleware({ } async function personalRecover( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { - const message: string = (req.params as string[])[0]; - const signature: string = (req.params as string[])[1]; - const signerAddress: string = sigUtil.recoverPersonalSignature({ + if ( + !req?.params || + !Array.isArray(req.params) || + !(req.params.length >= 2) + ) { + throw rpcErrors.invalidInput(); + } + + const params = req.params as [string, string]; + const message = params[0]; + const signature = params[1]; + const signerAddress = sigUtil.recoverPersonalSignature({ data: message, signature, }); @@ -319,36 +383,46 @@ export function createWalletMiddleware({ } async function encryptionPublicKey( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { if (!processEncryptionPublicKey) { throw rpcErrors.methodNotSupported(); } + if ( + !req?.params || + !Array.isArray(req.params) || + !(req.params.length >= 1) + ) { + throw rpcErrors.invalidInput(); + } + + const params = req.params as [string]; - const address: string = await validateAndNormalizeKeyholder( - (req.params as string[])[0], - req, - ); + const address = await validateAndNormalizeKeyholder(params[0], req); res.result = await processEncryptionPublicKey(address, req); } async function decryptMessage( - req: JsonRpcRequest, - res: PendingJsonRpcResponse, + req: JsonRpcRequest, + res: PendingJsonRpcResponse, ): Promise { if (!processDecryptMessage) { throw rpcErrors.methodNotSupported(); } + if ( + !req?.params || + !Array.isArray(req.params) || + !(req.params.length >= 1) + ) { + throw rpcErrors.invalidInput(); + } + const params = req.params as [string, string, Record?]; - const ciphertext: string = (req.params as string[])[0]; - const address: string = await validateAndNormalizeKeyholder( - (req.params as string[])[1], - req, - ); - const extraParams: Record = - (req.params as Record[])[2] || {}; + const ciphertext: string = params[0]; + const address: string = await validateAndNormalizeKeyholder(params[1], req); + const extraParams = params[2] || {}; const msgParams: MessageParams = { ...extraParams, from: address, @@ -373,7 +447,7 @@ export function createWalletMiddleware({ */ async function validateAndNormalizeKeyholder( address: string, - req: JsonRpcRequest, + req: JsonRpcRequest, ): Promise { if ( typeof address === 'string' && diff --git a/test/util/helpers.ts b/test/util/helpers.ts index c3b4b353..9acca7a3 100644 --- a/test/util/helpers.ts +++ b/test/util/helpers.ts @@ -1,10 +1,12 @@ import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; -import clone from 'clone'; +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; import type { + Json, + JsonRpcParams, JsonRpcRequest, JsonRpcResponse, - JsonRpcMiddleware, -} from 'json-rpc-engine'; +} from '@metamask/utils'; +import clone from 'clone'; import { isDeepStrictEqual } from 'util'; /** @@ -26,7 +28,10 @@ import { isDeepStrictEqual } from 'util'; * again, there will be no matching stub and an error will be thrown. This * feature is useful for making sure that all requests have canned responses. */ -export interface ProviderRequestStub { +export interface ProviderRequestStub< + Params extends JsonRpcParams, + Result extends Json, +> { request: Partial>; response: ( request: JsonRpcRequest, @@ -45,8 +50,8 @@ export interface ProviderRequestStub { * @returns The created middleware, as a mock function. */ export function buildFinalMiddlewareWithDefaultResponse< - Params, - Result, + Params extends JsonRpcParams, + Result extends Json, >(): JsonRpcMiddleware { return jest.fn((req, res, _next, end) => { if (res.id === undefined) { @@ -84,7 +89,7 @@ export function buildSimpleFinalMiddleware() { * it is optional. Given this, this function builds a `params` array for such an * endpoint with the given "block" param added at the end. * - * @param index - The index within the `params` array to add the "block" param. + * @param blockParamIndex - The index within the `params` array to add the "block" param. * @param blockParam - The desired "block" param to add. * @returns The mock params. */ @@ -110,7 +115,7 @@ export function buildMockParamsWithBlockParamAt( * such an endpoint, filling it with arbitrary values, but with the "block" * param missing. * - * @param index - The index within the `params` array where the "block" param + * @param blockParamIndex - The index within the `params` array where the "block" param * would* appear. * @returns The mock params. */ @@ -136,7 +141,7 @@ export function buildMockParamsWithoutBlockParamAt( */ export function buildStubForBlockNumberRequest( blockNumber = '0x0', -): ProviderRequestStub { +): ProviderRequestStub { return { request: { method: 'eth_blockNumber', @@ -160,9 +165,10 @@ export function buildStubForBlockNumberRequest( * @param requestStub - The request/response pair. * @returns The request/response pair, properly typed. */ -export function buildStubForGenericRequest( - requestStub: ProviderRequestStub, -) { +export function buildStubForGenericRequest< + Params extends JsonRpcParams, + Result extends Json, +>(requestStub: ProviderRequestStub) { return requestStub; } @@ -177,7 +183,7 @@ export function buildStubForGenericRequest( */ export function expectProviderRequestNotToHaveBeenMade( sendAsyncSpy: jest.SpyInstance, - requestMatcher: Partial>, + requestMatcher: Partial, ) { expect( sendAsyncSpy.mock.calls.some((args) => @@ -207,10 +213,7 @@ export function stubProviderRequests( stubs: ProviderRequestStub[], ) { const remainingStubs = clone(stubs); - const callNumbersByRequest = new Map< - Partial>, - number - >(); + const callNumbersByRequest = new Map, number>(); return jest.spyOn(provider, 'sendAsync').mockImplementation((request, cb) => { const stubIndex = remainingStubs.findIndex((stub) => requestMatches(stub.request, request), @@ -248,8 +251,8 @@ export function stubProviderRequests( * inside" the real request object. */ export function requestMatches( - requestMatcher: Partial>, - request: JsonRpcRequest, + requestMatcher: Partial, + request: JsonRpcRequest, ): boolean { return (Object.keys(requestMatcher) as (keyof typeof requestMatcher)[]).every( (key) => isDeepStrictEqual(requestMatcher[key], request[key]),