Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fe4e75b
feat: make SafeEventEmitterProvider compatible with eth req libraries
cryptodev-2s Jun 13, 2024
fd56e13
feat: add tsd for type testing
cryptodev-2s Jun 13, 2024
d754397
fix: unit tests
cryptodev-2s Jun 13, 2024
72a25cd
Merge branch 'main' into feature/make-SafeEventEmitterProvider-type-c…
cryptodev-2s Jun 13, 2024
e734a5d
fix: build
cryptodev-2s Jun 14, 2024
bd6070f
fix: test coverage
cryptodev-2s Jun 18, 2024
3bbf4db
Merge branch 'main' into feature/make-SafeEventEmitterProvider-type-c…
cryptodev-2s Jun 18, 2024
2bd3b24
Merge branch 'main' into feature/make-SafeEventEmitterProvider-type-c…
cryptodev-2s Jun 20, 2024
5338acc
fix: override request in fake provider
cryptodev-2s Jun 20, 2024
dc2f57b
fix: helper function unit tests
cryptodev-2s Jun 20, 2024
c99a02c
fix: remove tsd
cryptodev-2s Jun 20, 2024
2285231
fix: remove useless jest config update
cryptodev-2s Jun 20, 2024
7783d95
Merge branch 'main' into feature/make-SafeEventEmitterProvider-type-c…
cryptodev-2s Jun 25, 2024
403a862
Merge branch 'main' into feature/make-SafeEventEmitterProvider-type-c…
cryptodev-2s Jun 25, 2024
9f40d0e
Merge remote-tracking branch 'origin/main' into feature/make-SafeEven…
cryptodev-2s Jul 2, 2024
58443b4
fix: add smoke tests
cryptodev-2s Jul 2, 2024
c96d681
fix: move @metamask/utils to devDependencies
cryptodev-2s Jul 2, 2024
e87a334
fix: move back metamsk utils as dependencies
cryptodev-2s Jul 3, 2024
662b20e
fix: dependencies order
cryptodev-2s Jul 3, 2024
cf969dc
fix: request returned type and result
cryptodev-2s Jul 5, 2024
efda64b
Merge branch 'main' into feature/make-SafeEventEmitterProvider-type-c…
cryptodev-2s Jul 5, 2024
5a7887f
fix: remove useless export of Eip1193Request
cryptodev-2s Jul 5, 2024
640e821
fix: fake-provider
cryptodev-2s Jul 5, 2024
5ac0498
fix: test handle thrown error
cryptodev-2s Jul 5, 2024
17118e6
fix: remove optional chaining operator
cryptodev-2s Jul 5, 2024
8a9dd51
fix: thrown error
cryptodev-2s Jul 5, 2024
b3c426a
fix: add more tests for request method
cryptodev-2s Jul 5, 2024
88162ec
fix: add more tests for sendAsync and send
cryptodev-2s Jul 5, 2024
43608ec
fix: add more request failure tests
cryptodev-2s Jul 5, 2024
38dbabd
fix: request error unit tests
cryptodev-2s Jul 6, 2024
e19d25a
fix: drop error line
cryptodev-2s Jul 6, 2024
e9c69bb
fix: add :
cryptodev-2s Jul 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion constraints.pro
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ gen_enforced_field(WorkspaceCwd, 'scripts.changelog:update', CorrectChangelogUpd

% All non-root packages must have the same "test" script.
gen_enforced_field(WorkspaceCwd, 'scripts.test', 'jest --reporters=jest-silent-reporter') :-
WorkspaceCwd \= '.'.
WorkspaceCwd \= '.',
\+ workspace_field(WorkspaceCwd, 'name', '@metamask/eth-json-rpc-provider').

% All non-root packages must have the same "test:clean" script.
gen_enforced_field(WorkspaceCwd, 'scripts.test:clean', 'jest --clearCache') :-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ export class AssetsContractController extends BaseControllerV1<
throw new Error(MISSING_PROVIDER_ERROR);
}

// @ts-expect-error TODO: remove this annotation once the `Eip1193Provider` class is released
return new Web3Provider(provider);
}

Expand Down
3 changes: 3 additions & 0 deletions packages/eth-json-rpc-provider/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ module.exports = merge(baseConfig, {
// The display name when running multiple projects
displayName,

// *.test-d.ts files are tsd test files which should not be included in jest coverage
collectCoverageFrom: ['!./src/**/*.test-d.ts'],

// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
Expand Down
15 changes: 13 additions & 2 deletions packages/eth-json-rpc-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn lint:dependencies",
"lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern",
"publish:preview": "yarn npm publish --tag preview",
"test": "jest --reporters=jest-silent-reporter",
"test": "yarn test:source && yarn test:types",
"test:clean": "jest --clearCache",
"test:source": "jest --reporters=jest-silent-reporter",
"test:types": "tsd",
"test:verbose": "jest --verbose",
"test:watch": "jest --watch"
},
Expand All @@ -51,14 +53,20 @@
"@metamask/utils": "^8.3.0"
},
"devDependencies": {
"@ethersproject/providers": "^5.7.0",
"@metamask/auto-changelog": "^3.4.4",
"@metamask/eth-query": "^4.0.0",
"@metamask/ethjs-query": "^0.5.3",
"@types/jest": "^27.4.1",
"deepmerge": "^4.2.2",
"ethers": "^6.12.0",
"jest": "^27.5.1",
"jest-it-up": "^2.0.2",
"ts-jest": "^27.1.4",
"tsd": "^0.29.0",
"typedoc": "^0.24.8",
"typescript": "~4.9.5"
"typescript": "~4.9.5",
"uuid": "^8.3.2"
},
"packageManager": "[email protected]",
"engines": {
Expand All @@ -72,5 +80,8 @@
"allowScripts": {
"@lavamoat/preinstall-always-fail": false
}
},
"tsd": {
"directory": "src"
}
}
5 changes: 4 additions & 1 deletion packages/eth-json-rpc-provider/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export * from './provider-from-engine';
export * from './provider-from-middleware';
export { SafeEventEmitterProvider } from './safe-event-emitter-provider';
export {
SafeEventEmitterProvider,
type Eip1193Request,
} from './safe-event-emitter-provider';
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Web3Provider } from '@ethersproject/providers';
import EthQuery from '@metamask/eth-query';
import EthJsQuery from '@metamask/ethjs-query';
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
import { BrowserProvider } from 'ethers';

import { SafeEventEmitterProvider } from './safe-event-emitter-provider';

const engine = new JsonRpcEngine();
const provider = new SafeEventEmitterProvider({ engine });

// /* @metamask/eth-query */
new EthQuery(provider);

// /* @metamask/ethjs-query */
new EthJsQuery(provider);

// /* Ethers v5's Web3Provider */
new Web3Provider(provider);

// /* Ethers v6's BrowserProvider */
new BrowserProvider(provider);
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
import {
type JsonRpcSuccess,
type Json,
assertIsJsonRpcFailure,
} from '@metamask/utils';
import { promisify } from 'util';

import { SafeEventEmitterProvider } from './safe-event-emitter-provider';
import {
SafeEventEmitterProvider,
convertEip1193RequestToJsonRpcRequest,
} from './safe-event-emitter-provider';

describe('SafeEventEmitterProvider', () => {
describe('constructor', () => {
Expand Down Expand Up @@ -30,6 +38,44 @@ describe('SafeEventEmitterProvider', () => {
});
});

describe('request', () => {
it('handles a successful request', async () => {
const engine = new JsonRpcEngine();
engine.push((_req, res, _next, end) => {
res.result = 42;
end();
});
const provider = new SafeEventEmitterProvider({ engine });
const exampleRequest = {
id: 1,
jsonrpc: '2.0' as const,
method: 'test',
};

const response = await provider.request(exampleRequest);

expect((response as JsonRpcSuccess<Json>).result).toBe(42);
});

it('handles a failed request', async () => {
const engine = new JsonRpcEngine();
engine.push((_req, _res, _next, _end) => {
throw new Error('Test error');
});
const provider = new SafeEventEmitterProvider({ engine });
const exampleRequest = {
id: 1,
jsonrpc: '2.0' as const,
method: 'test',
};

const response = await provider.request(exampleRequest);

expect(response).toBeDefined();
assertIsJsonRpcFailure(response);
});
});

describe('sendAsync', () => {
it('handles a successful request', async () => {
const engine = new JsonRpcEngine();
Expand Down Expand Up @@ -122,3 +168,83 @@ describe('SafeEventEmitterProvider', () => {
});
});
});

describe('convertEip1193RequestToJsonRpcRequest', () => {
it('converts an EIP-1193 request to a JSON-RPC request', () => {
const eip1193Request = {
method: 'test',
params: { param1: 'value1', param2: 'value2' },
};

const jsonRpcRequest =
convertEip1193RequestToJsonRpcRequest(eip1193Request);

expect(jsonRpcRequest).toStrictEqual({
id: expect.any(String),
jsonrpc: '2.0',
method: 'test',
params: { param1: 'value1', param2: 'value2' },
});
});

it('generates a unique id if id is not provided', () => {
const eip1193Request = {
method: 'test',
params: { param1: 'value1', param2: 'value2' },
};

const jsonRpcRequest =
convertEip1193RequestToJsonRpcRequest(eip1193Request);

expect(jsonRpcRequest.id).toBeDefined();
expect(typeof jsonRpcRequest.id).toBe('string');
});

it('uses the provided id if id is provided', () => {
const eip1193Request = {
id: '123',
method: 'test',
params: { param1: 'value1', param2: 'value2' },
};
const jsonRpcRequest =
convertEip1193RequestToJsonRpcRequest(eip1193Request);

expect(jsonRpcRequest.id).toBe('123');
});

it('uses the default jsonrpc version if not provided', () => {
const eip1193Request = {
method: 'test',
params: { param1: 'value1', param2: 'value2' },
};

const jsonRpcRequest =
convertEip1193RequestToJsonRpcRequest(eip1193Request);

expect(jsonRpcRequest.jsonrpc).toBe('2.0');
});

it('uses the provided jsonrpc version if provided', () => {
const eip1193Request = {
jsonrpc: '2.0' as const,
method: 'test',
params: { param1: 'value1', param2: 'value2' },
};

const jsonRpcRequest =
convertEip1193RequestToJsonRpcRequest(eip1193Request);

expect(jsonRpcRequest.jsonrpc).toBe('2.0');
});

it('uses an empty object as params if not provided', () => {
const eip1193Request = {
method: 'test',
};

const jsonRpcRequest =
convertEip1193RequestToJsonRpcRequest(eip1193Request);

expect(jsonRpcRequest.params).toStrictEqual({});
});
});
88 changes: 77 additions & 11 deletions packages/eth-json-rpc-provider/src/safe-event-emitter-provider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,48 @@
import type { JsonRpcEngine } from '@metamask/json-rpc-engine';
import SafeEventEmitter from '@metamask/safe-event-emitter';
import type { JsonRpcRequest } from '@metamask/utils';
import type {
Json,
JsonRpcId,
JsonRpcParams,
JsonRpcRequest,
JsonRpcResponse,
JsonRpcVersion2,
} from '@metamask/utils';
import { v4 as uuidV4 } from 'uuid';

/**
* A JSON-RPC request conforming to the EIP-1193 specification.
*/
export type Eip1193Request<Params extends JsonRpcParams> = {
id?: JsonRpcId;
jsonrpc?: JsonRpcVersion2;
method: string;
params?: Params;
};

/**
* Converts an EIP-1193 request to a JSON-RPC request.
*
* @param eip1193Request - The EIP-1193 request to convert.
* @returns The corresponding JSON-RPC request.
*/
export function convertEip1193RequestToJsonRpcRequest<
Params extends JsonRpcParams,
>(eip1193Request: Eip1193Request<Params>) {
const {
id = uuidV4(),
jsonrpc = '2.0',
method,
params = {},
} = eip1193Request;
const jsonRpcRequest: JsonRpcRequest<Params | Record<never, never>> = {
id,
jsonrpc,
method,
params,
};
return jsonRpcRequest;
}

/**
* An Ethereum provider.
Expand Down Expand Up @@ -31,37 +73,61 @@ export class SafeEventEmitterProvider extends SafeEventEmitter {
/**
* Send a provider request asynchronously.
*
* @param req - The request to send.
* @param eip1193Request - The request to send.
* @returns The JSON-RPC response.
*/
async request<Params extends JsonRpcParams, Result extends Json>(
eip1193Request: Eip1193Request<Params>,
): Promise<JsonRpcResponse<Result>> {
const jsonRpcRequest =
convertEip1193RequestToJsonRpcRequest(eip1193Request);
return this.#engine.handle<Params | Record<never, never>, Result>(
jsonRpcRequest,
);
}

/**
* Send a provider request asynchronously.
*
* This method serves the same purpose as `request`. It only exists for
* legacy reasons.
*
* @param eip1193Request - The request to send.
* @param callback - A function that is called upon the success or failure of the request.
* @deprecated Please use `request` instead.
*/
sendAsync = (
req: JsonRpcRequest,
sendAsync = <Params extends JsonRpcParams>(
eip1193Request: Eip1193Request<Params>,
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback: (error: unknown, providerRes?: any) => void,
) => {
this.#engine.handle(req, callback);
const jsonRpcRequest =
convertEip1193RequestToJsonRpcRequest(eip1193Request);
this.#engine.handle(jsonRpcRequest, callback);
};

/**
* Send a provider request asynchronously.
*
* This method serves the same purpose as `sendAsync`. It only exists for
* This method serves the same purpose as `request`. It only exists for
* legacy reasons.
*
* @deprecated Use `sendAsync` instead.
* @param req - The request to send.
* @param eip1193Request - The request to send.
* @param callback - A function that is called upon the success or failure of the request.
* @deprecated Please use `sendAsync` instead.
*/
send = (
req: JsonRpcRequest,
send = <Params extends JsonRpcParams>(
eip1193Request: Eip1193Request<Params>,
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback: (error: unknown, providerRes?: any) => void,
) => {
if (typeof callback !== 'function') {
throw new Error('Must provide callback to "send" method.');
}
this.#engine.handle(req, callback);
const jsonRpcRequest =
convertEip1193RequestToJsonRpcRequest(eip1193Request);
this.#engine.handle(jsonRpcRequest, callback);
};
}
Loading