From c193b59403478b1b1f8abec17785a7c159db1b23 Mon Sep 17 00:00:00 2001 From: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Tue, 23 Jul 2024 10:50:36 +0200 Subject: [PATCH 1/8] adapt to eip-1193 provider changes (#317) --- package.json | 4 +- src/block-cache.test.ts | 8 +-- src/block-ref.test.ts | 50 ++++++---------- src/block-ref.ts | 15 +---- src/providerAsMiddleware.ts | 23 +++----- src/retryOnEmpty.test.ts | 109 ++++++++++++++--------------------- src/retryOnEmpty.ts | 60 ++++++++------------ test/util/helpers.ts | 110 ++++++++++++++++-------------------- yarn.lock | 33 +++++++---- 9 files changed, 167 insertions(+), 245 deletions(-) diff --git a/package.json b/package.json index 319d1509..4787dbaf 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "test:watch": "jest --watch" }, "dependencies": { - "@metamask/eth-block-tracker": "^10.0.0", - "@metamask/eth-json-rpc-provider": "^4.0.0", + "@metamask/eth-block-tracker": "^11.0.0", + "@metamask/eth-json-rpc-provider": "^4.1.0", "@metamask/eth-sig-util": "^7.0.0", "@metamask/json-rpc-engine": "^9.0.0", "@metamask/rpc-errors": "^6.0.0", diff --git a/src/block-cache.test.ts b/src/block-cache.test.ts index 34744593..07f2373c 100644 --- a/src/block-cache.test.ts +++ b/src/block-cache.test.ts @@ -23,11 +23,7 @@ function createTestSetup() { describe('block cache', () => { it('should cache a request and only hit the provider once', async () => { const { engine, provider, blockTracker } = createTestSetup(); - const spy = jest - .spyOn(provider, 'sendAsync') - .mockImplementation((req, cb) => { - cb(undefined, { id: req.id, result: '0x0', jsonrpc: '2.0' }); - }); + const requestSpy = jest.spyOn(provider, 'request').mockResolvedValue('0x0'); let hitCount = 0; const hitCountMiddleware = createHitTrackerMiddleware(); @@ -56,6 +52,6 @@ describe('block cache', () => { expect(hitCount).toBe(1); expect(response.result).toBe('0x0'); expect(response2.result).toBe('0x0'); - expect(spy).toHaveBeenCalled(); + expect(requestSpy).toHaveBeenCalled(); }); }); diff --git a/src/block-ref.test.ts b/src/block-ref.test.ts index 2d5f8947..7dea2d7d 100644 --- a/src/block-ref.test.ts +++ b/src/block-ref.test.ts @@ -10,7 +10,7 @@ import { stubProviderRequests, buildStubForBlockNumberRequest, buildStubForGenericRequest, - buildFinalMiddlewareWithDefaultResponse, + buildFinalMiddlewareWithDefaultResult, buildMockParamsWithoutBlockParamAt, expectProviderRequestNotToHaveBeenMade, } from '../test/util/helpers'; @@ -106,11 +106,7 @@ describe('createBlockRefMiddleware', () => { '0x100', ), }, - response: (req) => ({ - id: req.id, - jsonrpc: '2.0' as const, - result: 'something', - }), + result: async () => 'something', }), ]); @@ -120,14 +116,13 @@ describe('createBlockRefMiddleware', () => { id: 1, jsonrpc: '2.0', result: 'something', - error: undefined, }); }, ); }); it('does not proceed to the next middleware after making a request through the provider', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -161,11 +156,7 @@ describe('createBlockRefMiddleware', () => { '0x100', ), }, - response: (req) => ({ - id: req.id, - jsonrpc: '2.0' as const, - result: 'something', - }), + result: async () => 'something', }), ]); @@ -207,11 +198,7 @@ describe('createBlockRefMiddleware', () => { '0x100', ), }, - response: (req) => ({ - id: req.id, - jsonrpc: '2.0' as const, - result: 'something', - }), + result: async () => 'something', }), ]); @@ -221,14 +208,13 @@ describe('createBlockRefMiddleware', () => { id: 1, jsonrpc: '2.0', result: 'something', - error: undefined, }); }, ); }); it('does not proceed to the next middleware after making a request through the provider', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -259,11 +245,7 @@ describe('createBlockRefMiddleware', () => { '0x100', ), }, - response: (req) => ({ - id: req.id, - jsonrpc: '2.0' as const, - result: 'something', - }), + result: async () => 'something', }), ]); @@ -279,7 +261,7 @@ describe('createBlockRefMiddleware', () => { 'if the block param is something other than "latest", like %o', (blockParam) => { it('does not make a direct request through the provider', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -303,19 +285,19 @@ describe('createBlockRefMiddleware', () => { blockParam, ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest('0x100'), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); it('proceeds to the next middleware', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -366,7 +348,7 @@ describe('createBlockRefMiddleware', () => { describe('when the RPC method does not take a block parameter', () => { it('does not make a direct request through the provider', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -387,19 +369,19 @@ describe('createBlockRefMiddleware', () => { method: 'a_non_block_param_method', params: ['some value', '0x200'], }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest('0x100'), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); it('proceeds to the next middleware', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -465,7 +447,7 @@ async function withTestSetup( const { middlewareUnderTest, - otherMiddleware = [buildFinalMiddlewareWithDefaultResponse()], + otherMiddleware = [buildFinalMiddlewareWithDefaultResult()], } = configureMiddleware({ engine, provider, blockTracker }); for (const middleware of [middlewareUnderTest, ...otherMiddleware]) { diff --git a/src/block-ref.ts b/src/block-ref.ts index 949031c0..1348b8cc 100644 --- a/src/block-ref.ts +++ b/src/block-ref.ts @@ -2,13 +2,8 @@ import type { PollingBlockTracker } from '@metamask/eth-block-tracker'; import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; -import type { - Json, - JsonRpcParams, - PendingJsonRpcResponse, -} from '@metamask/utils'; +import type { Json, JsonRpcParams } from '@metamask/utils'; import { klona } from 'klona/full'; -import pify from 'pify'; import { projectLogger, createModuleLogger } from './logging-utils'; import type { Block } from './types'; @@ -68,12 +63,8 @@ export function createBlockRefMiddleware({ // perform child request log('Performing another request %o', childRequest); - const childRes: PendingJsonRpcResponse = await pify( - provider.sendAsync, - ).call(provider, childRequest); - // copy child response onto original response - res.result = childRes.result; - res.error = childRes.error; + // copy child result onto original response + res.result = await provider.request(childRequest); return undefined; }); diff --git a/src/providerAsMiddleware.ts b/src/providerAsMiddleware.ts index 70f620aa..c41e4262 100644 --- a/src/providerAsMiddleware.ts +++ b/src/providerAsMiddleware.ts @@ -1,5 +1,8 @@ import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; -import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import { + createAsyncMiddleware, + type JsonRpcMiddleware, +} from '@metamask/json-rpc-engine'; import type { Json, JsonRpcParams, @@ -9,21 +12,9 @@ import type { export function providerAsMiddleware( provider: SafeEventEmitterProvider, ): JsonRpcMiddleware { - return (req, res, _next, end) => { - // send request to provider - provider.sendAsync( - req, - (err: unknown, providerRes: PendingJsonRpcResponse) => { - // forward any error - if (err instanceof Error) { - return end(err); - } - // copy provider response onto original response - Object.assign(res, providerRes); - return end(); - }, - ); - }; + return createAsyncMiddleware(async (req, res) => { + res.result = await provider.request(req); + }); } export function ethersProviderAsMiddleware( diff --git a/src/retryOnEmpty.test.ts b/src/retryOnEmpty.test.ts index 27fefb3c..af541f29 100644 --- a/src/retryOnEmpty.test.ts +++ b/src/retryOnEmpty.test.ts @@ -3,13 +3,13 @@ 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 { errorCodes, rpcErrors } from '@metamask/rpc-errors'; +import { errorCodes, providerErrors, rpcErrors } from '@metamask/rpc-errors'; import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; import { createRetryOnEmptyMiddleware } from '.'; import type { ProviderRequestStub } from '../test/util/helpers'; import { - buildFinalMiddlewareWithDefaultResponse, + buildFinalMiddlewareWithDefaultResult, buildMockParamsWithBlockParamAt, buildMockParamsWithoutBlockParamAt, buildSimpleFinalMiddleware, @@ -142,22 +142,18 @@ describe('createRetryOnEmptyMiddleware', () => { blockNumber, ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest(blockNumber), stubRequestThatFailsThenFinallySucceeds({ request, numberOfTimesToFail: 9, - successfulResponse: (req) => ({ - id: req.id, - jsonrpc: '2.0', - result: 'something', - }), + successfulResult: async () => 'something', }), ]); const promiseForResponse = engine.handle(request); await waitForRequestToBeRetried({ - sendAsyncSpy, + requestSpy, request, numberOfTimes: 10, }); @@ -166,7 +162,6 @@ describe('createRetryOnEmptyMiddleware', () => { id: 1, jsonrpc: '2.0', result: 'something', - error: undefined, }); }, ); @@ -195,19 +190,12 @@ describe('createRetryOnEmptyMiddleware', () => { blockNumber, ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest(blockNumber), stubGenericRequest({ request, - response: (req) => { - return { - id: req.id, - jsonrpc: '2.0', - error: { - code: -1, - message: 'oops', - }, - }; + result: () => { + throw providerErrors.custom({ code: -1, message: 'oops' }); }, remainAfterUse: true, }), @@ -215,7 +203,7 @@ describe('createRetryOnEmptyMiddleware', () => { const promiseForResponse = engine.handle(request); await waitForRequestToBeRetried({ - sendAsyncSpy, + requestSpy, request, numberOfTimes: 10, }); @@ -263,13 +251,7 @@ describe('createRetryOnEmptyMiddleware', () => { buildStubForBlockNumberRequest(blockNumber), stubGenericRequest({ request, - response: (req) => { - return { - id: req.id, - jsonrpc: '2.0', - result: 'success', - }; - }, + result: async () => 'success', }), ]); @@ -303,13 +285,13 @@ describe('createRetryOnEmptyMiddleware', () => { '0x100', ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest('0x0'), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); @@ -376,13 +358,13 @@ describe('createRetryOnEmptyMiddleware', () => { blockParam, ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest('0x0'), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); @@ -450,13 +432,13 @@ describe('createRetryOnEmptyMiddleware', () => { blockParam, ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest(), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); @@ -519,13 +501,13 @@ describe('createRetryOnEmptyMiddleware', () => { method, params: buildMockParamsWithoutBlockParamAt(blockParamIndex), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest(), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); @@ -587,13 +569,13 @@ describe('createRetryOnEmptyMiddleware', () => { jsonrpc: '2.0', method, }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest(), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); @@ -653,7 +635,7 @@ describe('createRetryOnEmptyMiddleware', () => { buildStubForBlockNumberRequest(), { request, - response: () => { + result: () => { throw rpcErrors.invalidInput('execution reverted'); }, }, @@ -697,7 +679,7 @@ async function withTestSetup( const { middlewareUnderTest, - otherMiddleware = [buildFinalMiddlewareWithDefaultResponse()], + otherMiddleware = [buildFinalMiddlewareWithDefaultResult()], } = configureMiddleware({ engine, provider, blockTracker }); for (const middleware of [middlewareUnderTest, ...otherMiddleware]) { @@ -711,9 +693,9 @@ async function withTestSetup( } /** - * Builds a canned response for a request made to `provider.sendAsync`. Intended + * Builds a canned result for a request made to `provider.request`. Intended * to be used in conjunction with `stubProviderRequests`. Although not strictly - * necessary, it helps to assign a proper type to a request/response pair. + * necessary, it helps to assign a proper type to a request/result pair. * * @param requestStub - The request/response pair. * @returns The request/response pair, properly typed. @@ -725,16 +707,16 @@ function stubGenericRequest( } /** - * Builds a canned response for a request made to `provider.sendAsync` which + * Builds a canned result for a request made to `provider.request` which * will error for the first N instances and then succeed on the last instance. * Intended to be used in conjunction with `stubProviderRequests`. * * @param request - The request matcher for the stub. * @param numberOfTimesToFail - The number of times the request is expected to - * be called until it returns a successful response. - * @param successfulResponse - The response that `provider.sendAsync` will + * be called until it returns a successful result. + * @param successfulResult - The result that `provider.request` will * return when called past `numberOfTimesToFail`. - * @returns The request/response pair, properly typed. + * @returns The request/result pair, properly typed. */ function stubRequestThatFailsThenFinallySucceeds< T extends JsonRpcParams, @@ -742,27 +724,20 @@ function stubRequestThatFailsThenFinallySucceeds< >({ request, numberOfTimesToFail, - successfulResponse, + successfulResult, }: { request: ProviderRequestStub['request']; numberOfTimesToFail: number; - successfulResponse: ProviderRequestStub['response']; + successfulResult: ProviderRequestStub['result']; }): ProviderRequestStub { return stubGenericRequest({ request, - response: (req, callNumber) => { + result: async (callNumber) => { if (callNumber <= numberOfTimesToFail) { - return { - id: req.id, - jsonrpc: '2.0', - error: { - code: -1, - message: 'oops', - }, - }; + throw providerErrors.custom({ code: -1, message: 'oops' }); } - return successfulResponse(req, callNumber); + return await successfulResult(callNumber); }, remainAfterUse: true, }); @@ -770,30 +745,30 @@ function stubRequestThatFailsThenFinallySucceeds< /** * The `retryOnEmpty` middleware, as its name implies, uses the provider to make - * the given request, retrying said request up to 10 times if the response is + * the given request, retrying said request up to 10 times if the result is * empty before failing. Upon retrying, it will wait a brief time using * `setTimeout`. Because we are using Jest's fake timers, we have to manually * trigger the callback passed to `setTimeout` atfter it is called. The problem * is that we don't know when `setTimeout` will be called while the * `retryOnEmpty` middleware is running, so we have to wait. We do this by - * recording how many times `provider.sendAsync` has been called with the + * recording how many times `provider.request` has been called with the * request, and when that number goes up, we assume that `setTimeout` has been * called too and advance through time. We stop the loop when - * `provider.sendAsync` has been called the given number of times. + * `provider.request` has been called the given number of times. * * @param args - The arguments. - * @param sendAsyncSpy - The Jest spy object that represents - * `provider.sendAsync`. + * @param requestSpy - The Jest spy object that represents + * `provider.request`. * @param request - The request object. * @param numberOfTimes - The number of times that we expect - * `provider.sendAsync` to be called with `request`. + * `provider.request` to be called with `request`. */ async function waitForRequestToBeRetried({ - sendAsyncSpy, + requestSpy, request, numberOfTimes, }: { - sendAsyncSpy: jest.SpyInstance; + requestSpy: jest.SpyInstance; request: JsonRpcRequest; numberOfTimes: number; }) { @@ -803,7 +778,7 @@ async function waitForRequestToBeRetried({ await new Promise((resolve) => originalSetTimeout(resolve, 0)); if ( - sendAsyncSpy.mock.calls.filter((args) => requestMatches(args[0], request)) + requestSpy.mock.calls.filter((args) => requestMatches(args[0], request)) .length === iterationNumber ) { jest.runAllTimers(); diff --git a/src/retryOnEmpty.ts b/src/retryOnEmpty.ts index 79d34a71..ace8a61f 100644 --- a/src/retryOnEmpty.ts +++ b/src/retryOnEmpty.ts @@ -2,13 +2,8 @@ import type { PollingBlockTracker } from '@metamask/eth-block-tracker'; import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; -import type { - Json, - JsonRpcParams, - PendingJsonRpcResponse, -} from '@metamask/utils'; +import type { Json, JsonRpcParams } from '@metamask/utils'; import { klona } from 'klona/full'; -import pify from 'pify'; import { projectLogger, createModuleLogger } from './logging-utils'; import type { Block } from './types'; @@ -103,41 +98,34 @@ export function createRetryOnEmptyMiddleware({ // create child request with specific block-ref const childRequest = klona(req); // attempt child request until non-empty response is received - const childResponse: PendingJsonRpcResponse = await retry( - 10, - async () => { - log('Performing request %o', childRequest); - const attemptResponse: PendingJsonRpcResponse = await pify( - provider.sendAsync, - ).call(provider, childRequest); - log('Response is %o', attemptResponse); - // verify result - if (emptyValues.includes(attemptResponse.result as any)) { - throw new Error( - `RetryOnEmptyMiddleware - empty response "${JSON.stringify( - attemptResponse, - )}" for request "${JSON.stringify(childRequest)}"`, - ); - } - return attemptResponse; - }, - ); - log( - 'Copying result %o and error %o', - childResponse.result, - childResponse.error, - ); - // copy child response onto original response - res.result = childResponse.result; - res.error = childResponse.error; + const childResult = await retry(10, async () => { + log('Performing request %o', childRequest); + const attemptResult = await provider.request( + childRequest, + ); + log('Result is %o', attemptResult); + // verify result + const allEmptyValues: unknown[] = emptyValues; + if (allEmptyValues.includes(attemptResult)) { + throw new Error( + `RetryOnEmptyMiddleware - empty result "${JSON.stringify( + attemptResult, + )}" for request "${JSON.stringify(childRequest)}"`, + ); + } + return attemptResult; + }); + log('Copying result %o', childResult); + // copy child result onto original response + res.result = childResult; return undefined; }); } -async function retry( +async function retry( maxRetries: number, - asyncFn: () => Promise>, -): Promise> { + asyncFn: () => Promise, +): Promise { for (let index = 0; index < maxRetries; index++) { try { return await asyncFn(); diff --git a/test/util/helpers.ts b/test/util/helpers.ts index f6380304..46811f6c 100644 --- a/test/util/helpers.ts +++ b/test/util/helpers.ts @@ -1,58 +1,50 @@ import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; -import type { - Json, - JsonRpcParams, - JsonRpcRequest, - JsonRpcResponse, -} from '@metamask/utils'; +import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; import { klona } from 'klona/full'; import { isDeepStrictEqual } from 'util'; /** - * An object that can be used to assign a canned response to a request made via - * `provider.sendAsync`. + * An object that can be used to assign a canned result to a request made via + * `provider.request`. * * @template Params - The type that represents the request params. - * @template Result - The type that represents the response result. + * @template Result - The type that represents the result. * @property request - An object that represents a JsonRpcRequest. Keys such as * `id` or `jsonrpc` may be omitted if you don't care about them. - * @property response - A function that returns a JsonRpcResponse for that - * request. This function takes two arguments: the *real* request and a - * `callNumber`, which is the number of times the request has been made + * @property result - A function that returns a result for that request. + * This function takes `callNumber` argument, + * which is the number of times the request has been made * (counting the first request as 1). This latter argument be used to specify - * different responses for different instances of the same request. + * different results for different instances of the same request. * @property remainAfterUse - Usually, when a request is made via - * `provider.sendAsync`, the ProviderRequestStub which matches that request is + * `provider.request`, the ProviderRequestStub which matches that request is * removed from the list of stubs, so that if the same request comes through * 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. + * feature is useful for making sure that all requests have canned results. */ export interface ProviderRequestStub< Params extends JsonRpcParams, Result extends Json, > { request: Partial>; - response: ( - request: JsonRpcRequest, - callNumber: number, - ) => JsonRpcResponse; + result: (callNumber: number) => Promise; remainAfterUse?: boolean; } /** * Creates a middleware function that ends the request, but not before ensuring - * that the response has been filled with something. Additionally this function + * that the result has been filled with something. Additionally this function * is a Jest mock function so that you can make assertions on it. * * @template Params - The type that represents the request params. - * @template Result - The type that represents the response result. + * @template Result - The type that represents the result. * @returns The created middleware, as a mock function. */ -export function buildFinalMiddlewareWithDefaultResponse< +export function buildFinalMiddlewareWithDefaultResult< Params extends JsonRpcParams, Result extends Json, ->(): JsonRpcMiddleware { +>(): JsonRpcMiddleware { return jest.fn((req, res, _next, end) => { if (res.id === undefined) { res.id = req.id; @@ -63,7 +55,7 @@ export function buildFinalMiddlewareWithDefaultResponse< } if (res.result === undefined) { - res.result = 'default response'; + res.result = 'default result'; } end(); @@ -132,38 +124,34 @@ export function buildMockParamsWithoutBlockParamAt( } /** - * Builds a canned response for a `eth_blockNumber` request made to - * `provider.sendAsync` such that the response will return the given block + * Builds a canned result for a `eth_blockNumber` request made to + * `provider.request` such that the result will return the given block * number. Intended to be used in conjunction with `stubProviderRequests`. * * @param blockNumber - The block number (default: '0x0'). - * @returns The request/response pair. + * @returns The request/result pair. */ export function buildStubForBlockNumberRequest( blockNumber = '0x0', -): ProviderRequestStub { +): ProviderRequestStub { return { request: { method: 'eth_blockNumber', params: [], }, - response: (req) => ({ - id: req.id, - jsonrpc: '2.0', - result: blockNumber, - }), + result: async () => blockNumber, }; } /** - * Builds a canned response for a request made to `provider.sendAsync`. Intended + * Builds a canned result for a request made to `provider.request`. Intended * to be used in conjunction with `stubProviderRequests`. Although not strictly - * necessary, it helps to assign a proper type to a request/response pair. + * necessary, it helps to assign a proper type to a request/result pair. * * @template Params - The type that represents the request params. - * @template Result - The type that represents the response result. - * @param requestStub - The request/response pair. - * @returns The request/response pair, properly typed. + * @template Result - The type that represents the result. + * @param requestStub - The request/result pair. + * @returns The request/result pair, properly typed. */ export function buildStubForGenericRequest< Params extends JsonRpcParams, @@ -173,48 +161,48 @@ export function buildStubForGenericRequest< } /** - * Asserts that `provider.sendAsync` has not been called with the given request + * Asserts that `provider.request` has not been called with the given request * object (or an object that can matched to that request). * - * @param sendAsyncSpy - The Jest spy object that represents - * `provider.sendAsync`. + * @param requestSpy - The Jest spy object that represents + * `provider.request`. * @param requestMatcher - An object that can be matched to a request passed to - * `provider.sendAsync`. + * `provider.request`. */ export function expectProviderRequestNotToHaveBeenMade( - sendAsyncSpy: jest.SpyInstance, + requestSpy: jest.SpyInstance, requestMatcher: Partial, ) { expect( - sendAsyncSpy.mock.calls.some((args) => + requestSpy.mock.calls.some((args) => requestMatches(requestMatcher, args[0]), ), ).toBe(false); } /** - * Provides a way to assign specific responses to specific requests that are - * made through a provider. When `provider.sendAsync` is called, a stub matching + * Provides a way to assign specific results to specific requests that are + * made through a provider. When `provider.request` is called, a stub matching * the request will be looked for; if one is found, it is used and then * discarded, unless `remainAfterUse` is set for the stub. * * @param provider - The provider. * @param stubs - A series of pairs, where each pair specifies a request object - * — or part of one, at least — and a response for that request. The response - * is actually a function that takes two arguments: the *real* request and the - * number of times that that request has been made (counting the first as 1). - * This latter argument be used to specify different responses for different - * instances of the same request. The function should return a response object. - * @returns The Jest spy object that represents `provider.sendAsync` (so that + * — or part of one, at least — and a result for that request. The result + * is actually a function that takes one argument, which is the number of times + * that request has been made (counting the first as 1). + * This latter argument be used to specify different results for different + * instances of the same request. The function should return a result. + * @returns The Jest spy object that represents `provider.request` (so that * you can make assertions on the method later, if you like). */ export function stubProviderRequests( provider: SafeEventEmitterProvider, - stubs: ProviderRequestStub[], + stubs: ProviderRequestStub[], ) { const remainingStubs = klona(stubs); const callNumbersByRequest = new Map, number>(); - return jest.spyOn(provider, 'sendAsync').mockImplementation((request, cb) => { + return jest.spyOn(provider, 'request').mockImplementation(async (request) => { const stubIndex = remainingStubs.findIndex((stub) => requestMatches(stub.request, request), ); @@ -225,22 +213,22 @@ export function stubProviderRequests( const stub = remainingStubs[stubIndex]; const callNumber = callNumbersByRequest.get(stub.request) ?? 1; - cb(undefined, stub.response(request, callNumber)); - callNumbersByRequest.set(stub.request, callNumber + 1); if (!stub.remainAfterUse) { remainingStubs.splice(stubIndex, 1); } + + return await stub.result(callNumber); } }); } /** - * When using `stubProviderRequests` to list canned responses for specific - * requests that are made to `provider.sendAsync`, you don't need to provide the - * full request object to go along with the response, but only part of that - * request object. When `provider.sendAsync` is then called, we can look up the + * When using `stubProviderRequests` to list canned results for specific + * requests that are made to `provider.request`, you don't need to provide the + * full request object to go along with the result, but only part of that + * request object. When `provider.request` is then called, we can look up the * compare the real request object to the request object that was specified to * find a match. This function is used to do that comparison (and other * like comparisons). @@ -252,7 +240,7 @@ export function stubProviderRequests( */ export function requestMatches( requestMatcher: Partial, - request: JsonRpcRequest, + request: Partial, ): boolean { return (Object.keys(requestMatcher) as (keyof typeof requestMatcher)[]).every( (key) => isDeepStrictEqual(requestMatcher[key], request[key]), diff --git a/yarn.lock b/yarn.lock index 4563c8c2..a8c5904c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -928,16 +928,16 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-block-tracker@npm:^10.0.0": - version: 10.0.0 - resolution: "@metamask/eth-block-tracker@npm:10.0.0" +"@metamask/eth-block-tracker@npm:^11.0.0": + version: 11.0.0 + resolution: "@metamask/eth-block-tracker@npm:11.0.0" dependencies: - "@metamask/eth-json-rpc-provider": ^4.0.0 + "@metamask/eth-json-rpc-provider": ^4.1.0 "@metamask/safe-event-emitter": ^3.0.0 "@metamask/utils": ^8.1.0 json-rpc-random-id: ^1.0.1 pify: ^5.0.0 - checksum: 3b897a41305debe9828d6d18e079289f05e07f99d829f7425ce8703b16d00f3fcd1f108b34f946dee892400e30f4fe87d8fad66311ba8b46c39258ad875f83a6 + checksum: 27a2622cda97626c80119629108f739aa745b0c704649e00f6841aac10410f23c00ab7feb272539ccf209f092648b1538aa87c1247185ced01c2993b68fb8db5 languageName: node linkType: hard @@ -952,8 +952,8 @@ __metadata: "@metamask/eslint-config-jest": ^12.1.0 "@metamask/eslint-config-nodejs": ^12.1.0 "@metamask/eslint-config-typescript": ^12.1.0 - "@metamask/eth-block-tracker": ^10.0.0 - "@metamask/eth-json-rpc-provider": ^4.0.0 + "@metamask/eth-block-tracker": ^11.0.0 + "@metamask/eth-json-rpc-provider": ^4.1.0 "@metamask/eth-sig-util": ^7.0.0 "@metamask/json-rpc-engine": ^9.0.0 "@metamask/rpc-errors": ^6.0.0 @@ -987,14 +987,16 @@ __metadata: languageName: unknown linkType: soft -"@metamask/eth-json-rpc-provider@npm:^4.0.0": - version: 4.0.0 - resolution: "@metamask/eth-json-rpc-provider@npm:4.0.0" +"@metamask/eth-json-rpc-provider@npm:^4.1.0": + version: 4.1.0 + resolution: "@metamask/eth-json-rpc-provider@npm:4.1.0" dependencies: "@metamask/json-rpc-engine": ^9.0.0 + "@metamask/rpc-errors": ^6.2.1 "@metamask/safe-event-emitter": ^3.0.0 "@metamask/utils": ^8.3.0 - checksum: 4f8ad6a1737d54aeb83c5a1c7073a5cb17223e9cdacb0da4549aac7b57704b8239d9670c438eadf7974fe1167e59a9c54e6c32cce44b111c6514aae71429d6dd + uuid: ^8.3.2 + checksum: c9669c93df073423d36ff941b512247b569e7f7c56cc6110565bc8dc6590ad691a78d6d17eea6243721c1c464f0f008ea1326fc7373f90fb705fba5fb85d804d languageName: node linkType: hard @@ -6904,6 +6906,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df + languageName: node + linkType: hard + "uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" From 725b8c898f8f1004f3770b237f471894e190c0fc Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Tue, 23 Jul 2024 13:00:52 -0400 Subject: [PATCH 2/8] Bump deps to latest (#323) --- package.json | 12 ++--- yarn.lock | 135 ++++++++++++++++++++++++--------------------------- 2 files changed, 70 insertions(+), 77 deletions(-) diff --git a/package.json b/package.json index 4787dbaf..74dc9c25 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,12 @@ "test:watch": "jest --watch" }, "dependencies": { - "@metamask/eth-block-tracker": "^11.0.0", - "@metamask/eth-json-rpc-provider": "^4.1.0", - "@metamask/eth-sig-util": "^7.0.0", - "@metamask/json-rpc-engine": "^9.0.0", - "@metamask/rpc-errors": "^6.0.0", - "@metamask/utils": "^8.1.0", + "@metamask/eth-block-tracker": "^11.0.1", + "@metamask/eth-json-rpc-provider": "^4.1.1", + "@metamask/eth-sig-util": "^7.0.3", + "@metamask/json-rpc-engine": "^9.0.2", + "@metamask/rpc-errors": "^6.3.1", + "@metamask/utils": "^9.1.0", "@types/bn.js": "^5.1.5", "bn.js": "^5.2.1", "klona": "^2.0.6", diff --git a/yarn.lock b/yarn.lock index a8c5904c..d7c3bf4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -853,13 +853,13 @@ __metadata: languageName: node linkType: hard -"@metamask/abi-utils@npm:^2.0.2": - version: 2.0.2 - resolution: "@metamask/abi-utils@npm:2.0.2" +"@metamask/abi-utils@npm:^2.0.4": + version: 2.0.4 + resolution: "@metamask/abi-utils@npm:2.0.4" dependencies: - "@metamask/utils": ^8.0.0 - superstruct: ^1.0.3 - checksum: 5ec153e7691a4e1dc8738a0ba1a99a354ddb13851fa88a40a19f002f6308310e71c2cee28c3a25d9f7f67e839c7dffe4760e93e308dd17fa725b08d0dc73a3d4 + "@metamask/superstruct": ^3.1.0 + "@metamask/utils": ^9.0.0 + checksum: 85b15419248ddec1ab59ec5f3e41276f7509dadd9ced871658fa3cc04805ad35ace96986416aaecd24e3630e92b0ed078328966c92383ffa9b1cc3f0f357ad6c languageName: node linkType: hard @@ -928,16 +928,16 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-block-tracker@npm:^11.0.0": - version: 11.0.0 - resolution: "@metamask/eth-block-tracker@npm:11.0.0" +"@metamask/eth-block-tracker@npm:^11.0.1": + version: 11.0.1 + resolution: "@metamask/eth-block-tracker@npm:11.0.1" dependencies: - "@metamask/eth-json-rpc-provider": ^4.1.0 - "@metamask/safe-event-emitter": ^3.0.0 - "@metamask/utils": ^8.1.0 + "@metamask/eth-json-rpc-provider": ^4.1.1 + "@metamask/safe-event-emitter": ^3.1.1 + "@metamask/utils": ^9.1.0 json-rpc-random-id: ^1.0.1 pify: ^5.0.0 - checksum: 27a2622cda97626c80119629108f739aa745b0c704649e00f6841aac10410f23c00ab7feb272539ccf209f092648b1538aa87c1247185ced01c2993b68fb8db5 + checksum: 74c1259f7c2ee3074d5d5dc337fb81a89f562c755756210cd3eccc7fb9674c9101d58d88d6ac4c1a48a5676f85b99b87773819b26c52f41eae8e6ced454e11f9 languageName: node linkType: hard @@ -952,12 +952,12 @@ __metadata: "@metamask/eslint-config-jest": ^12.1.0 "@metamask/eslint-config-nodejs": ^12.1.0 "@metamask/eslint-config-typescript": ^12.1.0 - "@metamask/eth-block-tracker": ^11.0.0 - "@metamask/eth-json-rpc-provider": ^4.1.0 - "@metamask/eth-sig-util": ^7.0.0 - "@metamask/json-rpc-engine": ^9.0.0 - "@metamask/rpc-errors": ^6.0.0 - "@metamask/utils": ^8.1.0 + "@metamask/eth-block-tracker": ^11.0.1 + "@metamask/eth-json-rpc-provider": ^4.1.1 + "@metamask/eth-sig-util": ^7.0.3 + "@metamask/json-rpc-engine": ^9.0.2 + "@metamask/rpc-errors": ^6.3.1 + "@metamask/utils": ^9.1.0 "@types/bn.js": ^5.1.5 "@types/btoa": ^1.2.3 "@types/jest": ^27.4.1 @@ -987,75 +987,82 @@ __metadata: languageName: unknown linkType: soft -"@metamask/eth-json-rpc-provider@npm:^4.1.0": - version: 4.1.0 - resolution: "@metamask/eth-json-rpc-provider@npm:4.1.0" +"@metamask/eth-json-rpc-provider@npm:^4.1.1": + version: 4.1.1 + resolution: "@metamask/eth-json-rpc-provider@npm:4.1.1" dependencies: - "@metamask/json-rpc-engine": ^9.0.0 - "@metamask/rpc-errors": ^6.2.1 + "@metamask/json-rpc-engine": ^9.0.1 + "@metamask/rpc-errors": ^6.3.1 "@metamask/safe-event-emitter": ^3.0.0 - "@metamask/utils": ^8.3.0 + "@metamask/utils": ^9.0.0 uuid: ^8.3.2 - checksum: c9669c93df073423d36ff941b512247b569e7f7c56cc6110565bc8dc6590ad691a78d6d17eea6243721c1c464f0f008ea1326fc7373f90fb705fba5fb85d804d + checksum: a429d9511f33a62eb5f2f2f82f26d30a2a8f27d48aa3c8819d6ba03c2c0eef0e7eee7894a0899dd9af4b7d7f3e2092c25304bb34ab6e8d0daffee717f4109b5c languageName: node linkType: hard -"@metamask/eth-sig-util@npm:^7.0.0": - version: 7.0.1 - resolution: "@metamask/eth-sig-util@npm:7.0.1" +"@metamask/eth-sig-util@npm:^7.0.3": + version: 7.0.3 + resolution: "@metamask/eth-sig-util@npm:7.0.3" dependencies: "@ethereumjs/util": ^8.1.0 - "@metamask/abi-utils": ^2.0.2 - "@metamask/utils": ^8.1.0 + "@metamask/abi-utils": ^2.0.4 + "@metamask/utils": ^9.0.0 + "@scure/base": ~1.1.3 ethereum-cryptography: ^2.1.2 tweetnacl: ^1.0.3 - tweetnacl-util: ^0.15.1 - checksum: 98d056bd83aeb2d29ec3de09cd18e67d97ea295a59d405a9ce3fe274badd2d4f18da1fe530a266b4c777650855ed75ecd3577decd607a561e938dd7a808c5839 + checksum: fd4d0710857525815b241ddecce64988dd12303a9638577429baf180c62cf9cef9403aed01bc046b4860b332d455604c84e4b2a9b5997db16f444125b4b39398 languageName: node linkType: hard -"@metamask/json-rpc-engine@npm:^9.0.0": - version: 9.0.0 - resolution: "@metamask/json-rpc-engine@npm:9.0.0" +"@metamask/json-rpc-engine@npm:^9.0.1, @metamask/json-rpc-engine@npm:^9.0.2": + version: 9.0.2 + resolution: "@metamask/json-rpc-engine@npm:9.0.2" dependencies: - "@metamask/rpc-errors": ^6.2.1 + "@metamask/rpc-errors": ^6.3.1 "@metamask/safe-event-emitter": ^3.0.0 - "@metamask/utils": ^8.3.0 - checksum: b97170b36843145361015dabc5651df1d2c7f28f0756d3c9c05aef6a483098d562a9983cbe0e15f7fd1a66aa26481132b03ccb9061a2c48f0d3249c1f2348e97 + "@metamask/utils": ^9.1.0 + checksum: 4c852c9f30d05706ee497a2aca3ef6df12aabcff4a71a7426a27d95829f20cf2ff45c774eb9d95224bf16c9555a8cd7e44dccaea1bd44eda4dc43bf298885272 languageName: node linkType: hard -"@metamask/rpc-errors@npm:^6.0.0, @metamask/rpc-errors@npm:^6.2.1": - version: 6.2.1 - resolution: "@metamask/rpc-errors@npm:6.2.1" +"@metamask/rpc-errors@npm:^6.3.1": + version: 6.3.1 + resolution: "@metamask/rpc-errors@npm:6.3.1" dependencies: - "@metamask/utils": ^8.3.0 + "@metamask/utils": ^9.0.0 fast-safe-stringify: ^2.0.6 - checksum: a9223c3cb9ab05734ea0dda990597f90a7cdb143efa0c026b1a970f2094fe5fa3c341ed39b1e7623be13a96b98fb2c697ef51a2e2b87d8f048114841d35ee0a9 + checksum: 8761f5c0161cb3b342abd3ccccbd7b792f36a987e1f22c3f89b1bd29f72a2e35a2c91b58164fdd9dc3e5b67157500dcbdb5d04245117c14310c34cf42f7b8463 languageName: node linkType: hard -"@metamask/safe-event-emitter@npm:^3.0.0": - version: 3.0.0 - resolution: "@metamask/safe-event-emitter@npm:3.0.0" - checksum: 8dc58a76f9f75bf2405931465fc311c68043d851e6b8ebe9f82ae339073a08a83430dba9338f8e3adc4bfc8067607125074bcafa32baee3a5157f42343dc89e5 +"@metamask/safe-event-emitter@npm:^3.0.0, @metamask/safe-event-emitter@npm:^3.1.1": + version: 3.1.1 + resolution: "@metamask/safe-event-emitter@npm:3.1.1" + checksum: e24db4d7c20764bfc5b025065f92518c805f0ffb1da4820078b8cff7dcae964c0f354cf053fcb7ac659de015d5ffdf21aae5e8d44e191ee8faa9066855f22653 languageName: node linkType: hard -"@metamask/utils@npm:^8.0.0, @metamask/utils@npm:^8.1.0, @metamask/utils@npm:^8.3.0": - version: 8.4.0 - resolution: "@metamask/utils@npm:8.4.0" +"@metamask/superstruct@npm:^3.1.0": + version: 3.1.0 + resolution: "@metamask/superstruct@npm:3.1.0" + checksum: 00e4d0c0aae8b25ccc1885c1db0bb4ed1590010570140c255e4deee3bf8a10c859c8fce5e475b4ae09c8a56316207af87585b91f7f5a5c028d668ccd111f19e3 + languageName: node + linkType: hard + +"@metamask/utils@npm:^9.0.0, @metamask/utils@npm:^9.1.0": + version: 9.1.0 + resolution: "@metamask/utils@npm:9.1.0" dependencies: "@ethereumjs/tx": ^4.2.0 + "@metamask/superstruct": ^3.1.0 "@noble/hashes": ^1.3.1 "@scure/base": ^1.1.3 "@types/debug": ^4.1.7 debug: ^4.3.4 pony-cause: ^2.1.10 semver: ^7.5.4 - superstruct: ^1.0.3 uuid: ^9.0.1 - checksum: b0397e97bac7192f6189a8625a2dfcb56d3c2cf4dd2cb3d4e012a7e9786f04f59f6917805544bc131a6dacd2c8344e237ae43ad47429bb5eb35c6cf1248440b4 + checksum: 01f2c71a8f06158d5335bfe96bfd2f3aa39ec6b2323c5d0ff1d3136071a3e8ff7c1804d640ba1d4e07f96f3e68a95ff7729ddfcd34b373e5fefd86d6ef12d034 languageName: node linkType: hard @@ -1218,10 +1225,10 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:^1.1.3, @scure/base@npm:~1.1.0": - version: 1.1.3 - resolution: "@scure/base@npm:1.1.3" - checksum: 1606ab8a4db898cb3a1ada16c15437c3bce4e25854fadc8eb03ae93cbbbac1ed90655af4b0be3da37e12056fef11c0374499f69b9e658c9e5b7b3e06353c630c +"@scure/base@npm:^1.1.3, @scure/base@npm:~1.1.0, @scure/base@npm:~1.1.3": + version: 1.1.7 + resolution: "@scure/base@npm:1.1.7" + checksum: d9084be9a2f27971df1684af9e40bb750e86f549345e1bb3227fb61673c0c83569c92c1cb0a4ddccb32650b39d3cd3c145603b926ba751c9bc60c27317549b20 languageName: node linkType: hard @@ -6423,13 +6430,6 @@ __metadata: languageName: node linkType: hard -"superstruct@npm:^1.0.3": - version: 1.0.3 - resolution: "superstruct@npm:1.0.3" - checksum: 761790bb111e6e21ddd608299c252f3be35df543263a7ebbc004e840d01fcf8046794c274bcb351bdf3eae4600f79d317d085cdbb19ca05803a4361840cc9bb1 - languageName: node - linkType: hard - "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -6675,13 +6675,6 @@ __metadata: languageName: node linkType: hard -"tweetnacl-util@npm:^0.15.1": - version: 0.15.1 - resolution: "tweetnacl-util@npm:0.15.1" - checksum: ae6aa8a52cdd21a95103a4cc10657d6a2040b36c7a6da7b9d3ab811c6750a2d5db77e8c36969e75fdee11f511aa2b91c552496c6e8e989b6e490e54aca2864fc - languageName: node - linkType: hard - "tweetnacl@npm:^1.0.3": version: 1.0.3 resolution: "tweetnacl@npm:1.0.3" From a055a59ca10118a70d89fe90ca25392f31fff24b Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Wed, 24 Jul 2024 04:56:26 +0530 Subject: [PATCH 3/8] Changes in typed signature validation and normalization (#318) --- src/utils/normalize.test.ts | 32 ++------- src/utils/normalize.ts | 36 ++-------- src/wallet.test.ts | 135 +++++++++++++++++++++++++++++++++++- src/wallet.ts | 27 ++++++-- 4 files changed, 167 insertions(+), 63 deletions(-) diff --git a/src/utils/normalize.test.ts b/src/utils/normalize.test.ts index 8c743a4c..06cc2091 100644 --- a/src/utils/normalize.test.ts +++ b/src/utils/normalize.test.ts @@ -47,7 +47,7 @@ const MESSAGE_DATA_MOCK = { name: 'Liquid staked Ether 2.0', version: '2', chainId: '0x1', - verifyingContract: '996101235222674412020337938588541139382869425796', + verifyingContract: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', }, primaryType: 'Permit', message: { @@ -66,38 +66,16 @@ describe('normalizeTypedMessage', () => { } it('should normalize verifyingContract address in domain', () => { - const normalizedData = parseNormalizerResult(MESSAGE_DATA_MOCK); - expect(normalizedData.domain.verifyingContract).toBe( - '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', - ); - }); - - it('should normalize verifyingContract address in domain when provided data is an object', () => { - const NON_STRINGIFIED_MESSAGE_DATA_MOCK = MESSAGE_DATA_MOCK; - const normalizedData = JSON.parse( - normalizeTypedMessage( - NON_STRINGIFIED_MESSAGE_DATA_MOCK as unknown as string, - ), - ); - expect(normalizedData.domain.verifyingContract).toBe( - '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', - ); - }); - - it('should handle octal verifyingContract address by normalizing it', () => { - const expectedNormalizedOctalAddress = '0x53'; - const messageDataWithOctalAddress = { + const msgMock = { ...MESSAGE_DATA_MOCK, domain: { ...MESSAGE_DATA_MOCK.domain, - verifyingContract: '0o123', + verifyingContract: '0Xae7ab96520de3a18e5e111b5eaab095312d7fe84', }, }; - - const normalizedData = parseNormalizerResult(messageDataWithOctalAddress); - + const normalizedData = parseNormalizerResult(msgMock); expect(normalizedData.domain.verifyingContract).toBe( - expectedNormalizedOctalAddress, + '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', ); }); diff --git a/src/utils/normalize.ts b/src/utils/normalize.ts index f063e423..7637cbc9 100644 --- a/src/utils/normalize.ts +++ b/src/utils/normalize.ts @@ -1,9 +1,7 @@ -import { add0x, isValidHexAddress, isStrictHexString } from '@metamask/utils'; import type { Hex } from '@metamask/utils'; -import BN from 'bn.js'; type EIP712Domain = { - verifyingContract: string; + verifyingContract: Hex; }; type SignTypedMessageDataV3V4 = { @@ -45,7 +43,7 @@ export function normalizeTypedMessage(messageData: string) { * @param data - The messageData to parse. * @returns The data object for EIP712 normalization. */ -function parseTypedMessage(data: string) { +export function parseTypedMessage(data: string) { if (typeof data !== 'string') { return data; } @@ -54,36 +52,14 @@ function parseTypedMessage(data: string) { } /** - * Normalizes the address to a hexadecimal format + * Normalizes the address to standard hexadecimal format * * @param address - The address to normalize. * @returns The normalized address. */ -function normalizeContractAddress(address: string): Hex | string { - if (isStrictHexString(address) && isValidHexAddress(address)) { - return address; +function normalizeContractAddress(address: Hex): Hex { + if (address.startsWith('0X')) { + return `0x${address.slice(2)}`; } - - // Check if the address is in octal format, convert to hexadecimal - if (address.startsWith('0o')) { - // If octal, convert to hexadecimal - return octalToHex(address); - } - - // Check if the address is in decimal format, convert to hexadecimal - try { - const decimalBN = new BN(address, 10); - const hexString = decimalBN.toString(16); - return add0x(hexString); - } catch (e) { - // Ignore errors and return the original address - } - - // Returning the original address without normalization return address; } - -function octalToHex(octalAddress: string): Hex { - const decimalAddress = parseInt(octalAddress.slice(2), 8).toString(16); - return add0x(decimalAddress); -} diff --git a/src/wallet.test.ts b/src/wallet.test.ts index eaf29acb..9a8e783c 100644 --- a/src/wallet.test.ts +++ b/src/wallet.test.ts @@ -357,7 +357,7 @@ describe('wallet', () => { }, primaryType: 'EIP712Domain', domain: { - verifyingContract: '996101235222674412020337938588541139382869425796', + verifyingContract: '0Xae7ab96520de3a18e5e111b5eaab095312d7fe84', }, message: {}, }; @@ -391,6 +391,139 @@ describe('wallet', () => { signatureMethod: 'eth_signTypedData_v3', }); }); + + it('should throw if verifyingContract is invalid hex value', async () => { + const { engine } = createTestSetup(); + const getAccounts = async () => testAddresses.slice(); + const witnessedMsgParams: TypedMessageParams[] = []; + const processTypedMessageV3 = async (msgParams: TypedMessageParams) => { + witnessedMsgParams.push(msgParams); + // Assume testMsgSig is the expected signature result + return testMsgSig; + }; + + engine.push( + createWalletMiddleware({ getAccounts, processTypedMessageV3 }), + ); + + const message = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + }, + primaryType: 'EIP712Domain', + domain: { + verifyingContract: '917551056842671309452305380979543736893630245704', + }, + message: {}, + }; + + const stringifiedMessage = JSON.stringify(message); + + const payload = { + method: 'eth_signTypedData_v3', + params: [testAddresses[0], stringifiedMessage], // Assuming testAddresses[0] is a valid address from your setup + }; + + const promise = pify(engine.handle).call(engine, payload); + await expect(promise).rejects.toThrow('Invalid input.'); + }); + }); + + describe('signTypedDataV4', () => { + const getMsgParams = (verifyingContract?: string) => ({ + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Permit: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + }, + primaryType: 'Permit', + domain: { + name: 'MyToken', + version: '1', + verifyingContract: + verifyingContract ?? '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + chainId: '0x1', + }, + message: { + owner: testAddresses[0], + spender: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + value: 3000, + nonce: 0, + deadline: 50000000000, + }, + }); + + it('should not throw if request is permit with valid hex value for verifyingContract address', async () => { + const { engine } = createTestSetup(); + const getAccounts = async () => testAddresses.slice(); + const witnessedMsgParams: TypedMessageParams[] = []; + const processTypedMessageV4 = async (msgParams: TypedMessageParams) => { + witnessedMsgParams.push(msgParams); + // Assume testMsgSig is the expected signature result + return testMsgSig; + }; + + engine.push( + createWalletMiddleware({ getAccounts, processTypedMessageV4 }), + ); + + const payload = { + method: 'eth_signTypedData_v4', + params: [testAddresses[0], JSON.stringify(getMsgParams())], + }; + + const promise = pify(engine.handle).call(engine, payload); + const result = await promise; + expect(result).toStrictEqual({ + id: undefined, + jsonrpc: undefined, + result: + '0x68dc980608bceb5f99f691e62c32caccaee05317309015e9454eba1a14c3cd4505d1dd098b8339801239c9bcaac3c4df95569dcf307108b92f68711379be14d81c', + }); + }); + + it('should throw if request is permit with invalid hex value for verifyingContract address', async () => { + const { engine } = createTestSetup(); + const getAccounts = async () => testAddresses.slice(); + const witnessedMsgParams: TypedMessageParams[] = []; + const processTypedMessageV4 = async (msgParams: TypedMessageParams) => { + witnessedMsgParams.push(msgParams); + // Assume testMsgSig is the expected signature result + return testMsgSig; + }; + + engine.push( + createWalletMiddleware({ getAccounts, processTypedMessageV4 }), + ); + + const payload = { + method: 'eth_signTypedData_v4', + params: [ + testAddresses[0], + JSON.stringify( + getMsgParams('917551056842671309452305380979543736893630245704'), + ), + ], + }; + + const promise = pify(engine.handle).call(engine, payload); + await expect(promise).rejects.toThrow('Invalid input.'); + }); }); describe('sign', () => { diff --git a/src/wallet.ts b/src/wallet.ts index 58c8bb4d..db8eee6c 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -5,14 +5,15 @@ import { createScaffoldMiddleware, } from '@metamask/json-rpc-engine'; import { providerErrors, rpcErrors } from '@metamask/rpc-errors'; -import type { - Json, - JsonRpcRequest, - PendingJsonRpcResponse, +import { + isValidHexAddress, + type Json, + type JsonRpcRequest, + type PendingJsonRpcResponse, } from '@metamask/utils'; import type { Block } from './types'; -import { normalizeTypedMessage } from './utils/normalize'; +import { normalizeTypedMessage, parseTypedMessage } from './utils/normalize'; /* export type TransactionParams = { @@ -278,6 +279,7 @@ WalletMiddlewareOptions): JsonRpcMiddleware { const address = await validateAndNormalizeKeyholder(params[0], req); const message = normalizeTypedMessage(params[1]); + validateVerifyingContract(message); const version = 'V3'; const msgParams: TypedMessageParams = { data: message, @@ -308,6 +310,7 @@ WalletMiddlewareOptions): JsonRpcMiddleware { const address = await validateAndNormalizeKeyholder(params[0], req); const message = normalizeTypedMessage(params[1]); + validateVerifyingContract(message); const version = 'V4'; const msgParams: TypedMessageParams = { data: message, @@ -490,6 +493,20 @@ WalletMiddlewareOptions): JsonRpcMiddleware { } } +/** + * Validates verifyingContract of typedSignMessage. + * + * @param data - The data passed in typedSign request. + */ +function validateVerifyingContract(data: string) { + const { + domain: { verifyingContract }, + } = parseTypedMessage(data); + if (!isValidHexAddress(verifyingContract)) { + throw rpcErrors.invalidInput(); + } +} + function resemblesAddress(str: string): boolean { // hex prefix 2 + 20 bytes return str.length === 2 + 20 * 2; From b418bf39add31749d012de87b4966f58243d60a6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 10:01:17 -0400 Subject: [PATCH 4/8] 14.0.0 (#324) * 13.0.1 This is the release candidate for version 14.0.0. --------- Co-authored-by: github-actions Co-authored-by: Jongsun Suh Co-authored-by: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> --- CHANGELOG.md | 14 +++++++++++++- package.json | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27839423..4e45bcd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [14.0.0] +### Changed +- **BREAKING:** Adapt to EIP-1193 provider changes by replacing the deprecated `sendAsync` method with the `request` method ([#317](https://github.com/MetaMask/eth-json-rpc-middleware/pull/317)) + - **BREAKING:** Refactor `providerAsMiddleware` and middleware functions `retryOnEmpty`, `block-ref` to use the `request` method. +- Bump `@metamask/eth-block-tracker` from `^10.0.0` to `^11.0.1` ([#323](https://github.com/MetaMask/eth-json-rpc-middleware/pull/323)) +- Bump `@metamask/eth-json-rpc-provider` from `^4.0.0` to `^4.1.1` ([#323](https://github.com/MetaMask/eth-json-rpc-middleware/pull/323), [#317](https://github.com/MetaMask/eth-json-rpc-middleware/pull/317)) +- Bump `@metamask/eth-sig-util` from `^7.0.0` to `^7.0.3` ([#323](https://github.com/MetaMask/eth-json-rpc-middleware/pull/323)) +- Bump `@metamask/json-rpc-engine` from `^9.0.0` to `^9.0.2` ([#323](https://github.com/MetaMask/eth-json-rpc-middleware/pull/323)) +- Bump `@metamask/rpc-errors` from `^6.0.0` to `^6.3.1` ([#323](https://github.com/MetaMask/eth-json-rpc-middleware/pull/323)) +- Bump `@metamask/utils` from `^8.1.0` to `^9.1.0` ([#323](https://github.com/MetaMask/eth-json-rpc-middleware/pull/323)) + ## [13.0.0] ### Changed - **BREAKING**: Drop support for Node.js v16; add support for Node.js v20, v22 ([#312](https://github.com/MetaMask/eth-json-rpc-middleware/pull/312)) @@ -185,7 +196,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `json-rpc-engine@5.3.0` ([#53](https://github.com/MetaMask/eth-json-rpc-middleware/pull/53)) - `eth-rpc-errors@3.0.0` ([#55](https://github.com/MetaMask/eth-json-rpc-middleware/pull/55)) -[Unreleased]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v13.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v14.0.0...HEAD +[14.0.0]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v13.0.0...v14.0.0 [13.0.0]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v12.1.2...v13.0.0 [12.1.2]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v12.1.1...v12.1.2 [12.1.1]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v12.1.0...v12.1.1 diff --git a/package.json b/package.json index 74dc9c25..dbe4e495 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/eth-json-rpc-middleware", - "version": "13.0.0", + "version": "14.0.0", "description": "Ethereum-related json-rpc-engine middleware.", "repository": { "type": "git", From c66e89916f79b8d01bb05cc81d1f26566a66e6c9 Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Fri, 26 Jul 2024 16:59:53 -0500 Subject: [PATCH 5/8] remove eth_sign (#320) --- src/wallet.ts | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/src/wallet.ts b/src/wallet.ts index db8eee6c..0226d745 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -55,10 +55,6 @@ export interface WalletMiddlewareOptions { address: string, req: JsonRpcRequest, ) => Promise; - processEthSignMessage?: ( - msgParams: MessageParams, - req: JsonRpcRequest, - ) => Promise; processPersonalMessage?: ( msgParams: MessageParams, req: JsonRpcRequest, @@ -92,7 +88,6 @@ export function createWalletMiddleware({ getAccounts, processDecryptMessage, processEncryptionPublicKey, - processEthSignMessage, processPersonalMessage, processTransaction, processSignTransaction, @@ -113,7 +108,6 @@ WalletMiddlewareOptions): JsonRpcMiddleware { eth_sendTransaction: createAsyncMiddleware(sendTransaction), eth_signTransaction: createAsyncMiddleware(signTransaction), // message signatures - eth_sign: createAsyncMiddleware(ethSign), eth_signTypedData: createAsyncMiddleware(signTypedData), eth_signTypedData_v3: createAsyncMiddleware(signTypedDataV3), eth_signTypedData_v4: createAsyncMiddleware(signTypedDataV4), @@ -195,36 +189,6 @@ WalletMiddlewareOptions): JsonRpcMiddleware { // // message signatures // - - async function ethSign( - 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 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, - data: message, - signatureMethod: 'eth_sign', - }; - - res.result = await processEthSignMessage(msgParams, req); - } - async function signTypedData( req: JsonRpcRequest, res: PendingJsonRpcResponse, From 17d1ac92d71280b4ba6f4baeedde90c2cea55043 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Tue, 20 Aug 2024 11:14:35 -0400 Subject: [PATCH 6/8] Add changelog entries for `#318` (#327) --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e45bcd5..927bb61b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/rpc-errors` from `^6.0.0` to `^6.3.1` ([#323](https://github.com/MetaMask/eth-json-rpc-middleware/pull/323)) - Bump `@metamask/utils` from `^8.1.0` to `^9.1.0` ([#323](https://github.com/MetaMask/eth-json-rpc-middleware/pull/323)) +### Security +- **BREAKING:** Typed signature validation only replaces `0X` prefix with `0x`, and contract address normalization is removed for decimal and octal values ([#318](https://github.com/MetaMask/eth-json-rpc-middleware/pull/318)) + - Threat actors have been manipulating `eth_signTypedData_v4` fields to cause failures in blockaid's detectors. + - Extension crashes with an error when performing Malicious permit with a non-0x prefixed integer address. + - This fixes an issue where the key value row or petname component disappears if a signed address is prefixed by "0X" instead of "0x". + ## [13.0.0] ### Changed - **BREAKING**: Drop support for Node.js v16; add support for Node.js v20, v22 ([#312](https://github.com/MetaMask/eth-json-rpc-middleware/pull/312)) From 25c0bed1730f1f3093f8c264a6d9595a64d0d77f Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Wed, 4 Sep 2024 22:37:30 +0530 Subject: [PATCH 7/8] Request validation should not throw if verifyingContract is not defined in typed signature (#328) --- src/wallet.test.ts | 73 ++++++++++++++++++++++++++++++++++++++++++++++ src/wallet.ts | 6 ++-- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/wallet.test.ts b/src/wallet.test.ts index 9a8e783c..a58efb81 100644 --- a/src/wallet.test.ts +++ b/src/wallet.test.ts @@ -432,6 +432,50 @@ describe('wallet', () => { const promise = pify(engine.handle).call(engine, payload); await expect(promise).rejects.toThrow('Invalid input.'); }); + + it('should not throw if verifyingContract is undefined', async () => { + const { engine } = createTestSetup(); + const getAccounts = async () => testAddresses.slice(); + const witnessedMsgParams: TypedMessageParams[] = []; + const processTypedMessageV3 = async (msgParams: TypedMessageParams) => { + witnessedMsgParams.push(msgParams); + // Assume testMsgSig is the expected signature result + return testMsgSig; + }; + + engine.push( + createWalletMiddleware({ getAccounts, processTypedMessageV3 }), + ); + + const message = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + }, + primaryType: 'EIP712Domain', + message: {}, + }; + + const stringifiedMessage = JSON.stringify(message); + + const payload = { + method: 'eth_signTypedData_v3', + params: [testAddresses[0], stringifiedMessage], // Assuming testAddresses[0] is a valid address from your setup + }; + + const promise = pify(engine.handle).call(engine, payload); + const result = await promise; + expect(result).toStrictEqual({ + id: undefined, + jsonrpc: undefined, + result: + '0x68dc980608bceb5f99f691e62c32caccaee05317309015e9454eba1a14c3cd4505d1dd098b8339801239c9bcaac3c4df95569dcf307108b92f68711379be14d81c', + }); + }); }); describe('signTypedDataV4', () => { @@ -524,6 +568,35 @@ describe('wallet', () => { const promise = pify(engine.handle).call(engine, payload); await expect(promise).rejects.toThrow('Invalid input.'); }); + + it('should not throw if request is permit with undefined value for verifyingContract address', async () => { + const { engine } = createTestSetup(); + const getAccounts = async () => testAddresses.slice(); + const witnessedMsgParams: TypedMessageParams[] = []; + const processTypedMessageV4 = async (msgParams: TypedMessageParams) => { + witnessedMsgParams.push(msgParams); + // Assume testMsgSig is the expected signature result + return testMsgSig; + }; + + engine.push( + createWalletMiddleware({ getAccounts, processTypedMessageV4 }), + ); + + const payload = { + method: 'eth_signTypedData_v4', + params: [testAddresses[0], JSON.stringify(getMsgParams())], + }; + + const promise = pify(engine.handle).call(engine, payload); + const result = await promise; + expect(result).toStrictEqual({ + id: undefined, + jsonrpc: undefined, + result: + '0x68dc980608bceb5f99f691e62c32caccaee05317309015e9454eba1a14c3cd4505d1dd098b8339801239c9bcaac3c4df95569dcf307108b92f68711379be14d81c', + }); + }); }); describe('sign', () => { diff --git a/src/wallet.ts b/src/wallet.ts index 0226d745..a1e65613 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -463,10 +463,8 @@ WalletMiddlewareOptions): JsonRpcMiddleware { * @param data - The data passed in typedSign request. */ function validateVerifyingContract(data: string) { - const { - domain: { verifyingContract }, - } = parseTypedMessage(data); - if (!isValidHexAddress(verifyingContract)) { + const { domain: { verifyingContract } = {} } = parseTypedMessage(data); + if (verifyingContract && !isValidHexAddress(verifyingContract)) { throw rpcErrors.invalidInput(); } } From 1d17a1d802ce83da600021c06ea0275e7ae859a0 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 6 Sep 2024 14:53:27 -0230 Subject: [PATCH 8/8] Update `main` with changes from v14.0.1 (#332) * Request validation should not throw if verifyingContract is not defined in typed signature (#328) (#330) * 14.0.1 (#331) --------- Co-authored-by: Jyoti Puri --- CHANGELOG.md | 7 ++++++- package.json | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 927bb61b..11db7260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [14.0.1] +### Fixed +- Request validation should not throw if verifyingContract is not defined in typed signature ([#328](https://github.com/MetaMask/eth-json-rpc-middleware/pull/328)) + ## [14.0.0] ### Changed - **BREAKING:** Adapt to EIP-1193 provider changes by replacing the deprecated `sendAsync` method with the `request` method ([#317](https://github.com/MetaMask/eth-json-rpc-middleware/pull/317)) @@ -202,7 +206,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `json-rpc-engine@5.3.0` ([#53](https://github.com/MetaMask/eth-json-rpc-middleware/pull/53)) - `eth-rpc-errors@3.0.0` ([#55](https://github.com/MetaMask/eth-json-rpc-middleware/pull/55)) -[Unreleased]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v14.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v14.0.1...HEAD +[14.0.1]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v14.0.0...v14.0.1 [14.0.0]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v13.0.0...v14.0.0 [13.0.0]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v12.1.2...v13.0.0 [12.1.2]: https://github.com/MetaMask/eth-json-rpc-middleware/compare/v12.1.1...v12.1.2 diff --git a/package.json b/package.json index dbe4e495..54125b6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/eth-json-rpc-middleware", - "version": "14.0.0", + "version": "14.0.1", "description": "Ethereum-related json-rpc-engine middleware.", "repository": { "type": "git",