diff --git a/.circleci/config.yml b/.circleci/config.yml index f800ab484ebe..9793dafb33df 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -190,6 +190,10 @@ workflows: requires: - prep-build-test - get-changed-files-with-git-diff + - test-api-specs-multichain: + requires: + - prep-build-test-flask + - get-changed-files-with-git-diff - test-e2e-chrome-multiple-providers: requires: - prep-build-test @@ -911,6 +915,37 @@ jobs: - store_test_results: path: test/test-results/e2e + test-api-specs-multichain: + executor: node-browsers-medium-plus + steps: + - run: *shallow-git-clone-and-enable-vnc + - run: sudo corepack enable + - attach_workspace: + at: . + - run: + name: Move test build to dist + command: mv ./dist-test-flask ./dist + - run: + name: Move test zips to builds + command: mv ./builds-test-flask ./builds + - gh/install + - run: + name: test:api-specs-multichain + command: .circleci/scripts/test-run-e2e.sh yarn test:api-specs-multichain + no_output_timeout: 5m + - run: + name: Comment on PR + command: | + if [ -f html-report-multichain/index.html ]; then + gh pr comment "${CIRCLE_PR_NUMBER}" --body ":x: Multichain API Spec Test Failed. View the report [here](https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/${CIRCLE_NODE_INDEX}/html-report-multichain/index.html)." + else + echo "Multichain API Spec Report not found!" + fi + when: on_fail + - store_artifacts: + path: html-report-multichain + destination: html-report-multichain + test-api-specs: executor: node-browsers-medium-plus steps: diff --git a/.gitignore b/.gitignore index 6ee150dd8653..0dc60549e88f 100644 --- a/.gitignore +++ b/.gitignore @@ -78,10 +78,13 @@ lavamoat/**/policy-debug.json # Attributions licenseInfos.json +# Branding +/app/images/branding + # API Spec tests html-report/ +html-report-multichain/ -/app/images/branding /changed-files # UI Integration tests diff --git a/app/build-types/flask/manifest/_base.json b/app/build-types/flask/manifest/_base.json index bc43d646a9bc..2d1366b53c7a 100644 --- a/app/build-types/flask/manifest/_base.json +++ b/app/build-types/flask/manifest/_base.json @@ -11,6 +11,10 @@ }, "default_title": "MetaMask Flask" }, + "externally_connectable": { + "matches": ["http://*/*", "https://*/*"], + "ids": ["*"] + }, "icons": { "16": "images/icon-16.png", "19": "images/icon-19.png", diff --git a/app/manifest/v2/_barad_dur.json b/app/manifest/v2/_barad_dur.json deleted file mode 100644 index 304ebf8c4a24..000000000000 --- a/app/manifest/v2/_barad_dur.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "externally_connectable": { - "matches": ["http://*/*", "https://*/*"], - "ids": ["*"] - } -} diff --git a/app/manifest/v3/_barad_dur.json b/app/manifest/v3/_barad_dur.json deleted file mode 100644 index 304ebf8c4a24..000000000000 --- a/app/manifest/v3/_barad_dur.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "externally_connectable": { - "matches": ["http://*/*", "https://*/*"], - "ids": ["*"] - } -} diff --git a/app/scripts/background.js b/app/scripts/background.js index 115314fbed02..49b9d4c485cd 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -365,7 +365,9 @@ function overrideContentSecurityPolicyHeader() { // These are set after initialization let connectRemote; let connectExternalExtension; +///: BEGIN:ONLY_INCLUDE_IF(build-flask) let connectExternalCaip; +///: END:ONLY_INCLUDE_IF browser.runtime.onConnect.addListener(async (...args) => { // Queue up connection attempts here, waiting until after initialization @@ -378,13 +380,18 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { // Queue up connection attempts here, waiting until after initialization await isInitialized; // This is set in `setupController`, which is called as part of initialization - const port = args[0]; - if (port.sender.tab?.id && process.env.BARAD_DUR) { + ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-mmi) + connectExternalExtension(...args); + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + const port = args[0]; + if (port.sender.tab?.id) { connectExternalCaip(...args); } else { connectExternalExtension(...args); } + ///: END:ONLY_INCLUDE_IF }); function saveTimestamp() { @@ -1017,6 +1024,7 @@ export function setupController( }); }; + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) connectExternalCaip = async (remotePort) => { if (metamaskBlockedPorts.includes(remotePort.name)) { return; @@ -1035,6 +1043,7 @@ export function setupController( sender: remotePort.sender, }); }; + ///: END:ONLY_INCLUDE_IF if (overrides?.registerConnectListeners) { overrides.registerConnectListeners(connectRemote, connectExternalExtension); diff --git a/app/scripts/controllers/permissions/enums.ts b/app/scripts/controllers/permissions/enums.ts index c170bd78aa67..9210d6751bdc 100644 --- a/app/scripts/controllers/permissions/enums.ts +++ b/app/scripts/controllers/permissions/enums.ts @@ -2,4 +2,5 @@ export enum NOTIFICATION_NAMES { accountsChanged = 'metamask_accountsChanged', unlockStateChanged = 'metamask_unlockStateChanged', chainChanged = 'metamask_chainChanged', + sessionChanged = 'wallet_sessionChanged', } diff --git a/app/scripts/controllers/permissions/selectors.js b/app/scripts/controllers/permissions/selectors.js index 97464885b7a6..df244681ad8c 100644 --- a/app/scripts/controllers/permissions/selectors.js +++ b/app/scripts/controllers/permissions/selectors.js @@ -44,6 +44,33 @@ export const getPermittedAccountsByOrigin = createSelector( }, ); +/** + * Get the authorized CAIP-25 scopes for each subject, keyed by origin. + * The values of the returned map are immutable values from the + * PermissionController state. + * + * @returns {Map} The current origin:authorization map. + */ +export const getAuthorizedScopesByOrigin = createSelector( + getSubjects, + (subjects) => { + return Object.values(subjects).reduce( + (originToAuthorizationsMap, subject) => { + const caveats = + subject.permissions?.[Caip25EndowmentPermissionName]?.caveats || []; + + const caveat = caveats.find(({ type }) => type === Caip25CaveatType); + + if (caveat) { + originToAuthorizationsMap.set(subject.origin, caveat.value); + } + return originToAuthorizationsMap; + }, + new Map(), + ); + }, +); + /** * Get the permitted chains for each subject, keyed by origin. * The values of the returned map are immutable values from the @@ -112,3 +139,89 @@ export const diffMap = (currentMap, previousMap) => { } return changedMap; }; + +/** + * Given the current and previous exposed CAIP-25 authorization for each PermissionController + * subject, returns a new map containing all authorizations that have changed. + * The values of each map must be immutable values directly from the + * PermissionController state, or an empty object instantiated in this + * function. + * + * @param {Map} newAuthorizationsMap - The new origin:authorization map. + * @param {Map} [previousAuthorizationsMap] - The previous origin:authorization map. + * @returns {Map} The origin:authorization map of changed authorizations. + */ +export const getChangedAuthorizations = ( + newAuthorizationsMap, + previousAuthorizationsMap, +) => { + if (previousAuthorizationsMap === undefined) { + return newAuthorizationsMap; + } + + const changedAuthorizations = new Map(); + if (newAuthorizationsMap === previousAuthorizationsMap) { + return changedAuthorizations; + } + + const newOrigins = new Set([...newAuthorizationsMap.keys()]); + + for (const origin of previousAuthorizationsMap.keys()) { + const newAuthorizations = newAuthorizationsMap.get(origin) ?? { + requiredScopes: {}, + optionalScopes: {}, + }; + + // The values of these maps are references to immutable values, which is why + // a strict equality check is enough for diffing. The values are either from + // PermissionController state, or an empty object initialized in the previous + // call to this function. `newAuthorizationsMap` will never contain any empty + // objects. + if (previousAuthorizationsMap.get(origin) !== newAuthorizations) { + changedAuthorizations.set(origin, newAuthorizations); + } + + newOrigins.delete(origin); + } + + // By now, newOrigins is either empty or contains some number of previously + // unencountered origins, and all of their authorizations have "changed". + for (const origin of newOrigins.keys()) { + changedAuthorizations.set(origin, newAuthorizationsMap.get(origin)); + } + return changedAuthorizations; +}; + +/** + * + * @param {Map} newAuthorizationsMap - The new origin:authorization map. + * @param {Map} [previousAuthorizationsMap] - The previous origin:authorization map. + * @returns {Map} The origin:authorization map of changed authorizations. + */ +export const getRemovedAuthorizations = ( + newAuthorizationsMap, + previousAuthorizationsMap, +) => { + const removedAuthorizations = new Map(); + + // If there are no previous authorizations, there are no removed authorizations. + // OR If the new authorizations map is the same as the previous authorizations map, + // there are no removed authorizations + if ( + previousAuthorizationsMap === undefined || + newAuthorizationsMap === previousAuthorizationsMap + ) { + return removedAuthorizations; + } + + const previousOrigins = new Set([...previousAuthorizationsMap.keys()]); + for (const origin of newAuthorizationsMap.keys()) { + previousOrigins.delete(origin); + } + + for (const origin of previousOrigins.keys()) { + removedAuthorizations.set(origin, previousAuthorizationsMap.get(origin)); + } + + return removedAuthorizations; +}; diff --git a/app/scripts/controllers/permissions/selectors.test.js b/app/scripts/controllers/permissions/selectors.test.js index 9a6cc10a9a07..11524e373d3d 100644 --- a/app/scripts/controllers/permissions/selectors.test.js +++ b/app/scripts/controllers/permissions/selectors.test.js @@ -7,6 +7,7 @@ import { diffMap, getPermittedAccountsByOrigin, getPermittedChainsByOrigin, + getRemovedAuthorizations, } from './selectors'; describe('PermissionController selectors', () => { @@ -167,6 +168,36 @@ describe('PermissionController selectors', () => { }); }); + describe('getRemovedAuthorizations', () => { + it('returns an empty map if the new and previous values are the same', () => { + const newAuthorizations = new Map(); + expect( + getRemovedAuthorizations(newAuthorizations, newAuthorizations), + ).toStrictEqual(new Map()); + }); + + it('returns a new map of the removed authorizations if the new and previous values differ', () => { + const mockAuthorization = { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + }, + optionalScopes: {}, + }; + const previousAuthorizations = new Map([ + ['foo.bar', mockAuthorization], + ['bar.baz', mockAuthorization], + ]); + + const newAuthorizations = new Map([['foo.bar', mockAuthorization]]); + + expect( + getRemovedAuthorizations(newAuthorizations, previousAuthorizations), + ).toStrictEqual(new Map([['bar.baz', mockAuthorization]])); + }); + }); + describe('getPermittedChainsByOrigin', () => { it('memoizes and gets permitted chains by origin', () => { const state1 = { diff --git a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js index 9be36a3dbced..e3325612d7aa 100644 --- a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js +++ b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js @@ -28,6 +28,10 @@ export const createEthAccountsMethodMiddleware = makeMethodMiddlewareMaker([ ethAccountsHandler, ]); +// The primary home of RPC method implementations for the MultiChain API. +export const createMultichainMethodMiddleware = + makeMethodMiddlewareMaker(localHandlers); + /** * Creates a method middleware factory function given a set of method handlers. * @@ -35,7 +39,7 @@ export const createEthAccountsMethodMiddleware = makeMethodMiddlewareMaker([ * handler implementations. * @returns The method middleware factory function. */ -function makeMethodMiddlewareMaker(handlers) { +export function makeMethodMiddlewareMaker(handlers) { const handlerMap = handlers.reduce((map, handler) => { for (const methodName of handler.methodNames) { map[methodName] = handler; diff --git a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.test.js b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.test.js index 4a3b9f958a16..b919b195db5c 100644 --- a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.test.js +++ b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.test.js @@ -6,6 +6,7 @@ import { import { createEip1193MethodMiddleware, createEthAccountsMethodMiddleware, + createMultichainMethodMiddleware, } from '.'; const getHandler = () => ({ @@ -60,6 +61,7 @@ jest.mock('./handlers', () => ({ describe.each([ ['createEip1193MethodMiddleware', createEip1193MethodMiddleware], ['createEthAccountsMethodMiddleware', createEthAccountsMethodMiddleware], + ['createMultichainMethodMiddleware', createMultichainMethodMiddleware], ])('%s', (_name, createMiddleware) => { const method1 = 'method1'; diff --git a/app/scripts/lib/rpc-method-middleware/createUnsupportedMethodMiddleware.test.ts b/app/scripts/lib/rpc-method-middleware/createUnsupportedMethodMiddleware.test.ts index e50c86d13268..418d22c1821d 100644 --- a/app/scripts/lib/rpc-method-middleware/createUnsupportedMethodMiddleware.test.ts +++ b/app/scripts/lib/rpc-method-middleware/createUnsupportedMethodMiddleware.test.ts @@ -10,8 +10,8 @@ describe('createUnsupportedMethodMiddleware', () => { }); const getMockResponse = () => ({ jsonrpc: jsonrpc2, id: 'foo' }); - it('forwards requests whose methods are not on the list of unsupported methods', () => { - const middleware = createUnsupportedMethodMiddleware(); + it('forwards requests whose methods are not in the list of unsupported methods', () => { + const middleware = createUnsupportedMethodMiddleware([]); const nextMock = jest.fn(); const endMock = jest.fn(); @@ -22,10 +22,12 @@ describe('createUnsupportedMethodMiddleware', () => { }); // @ts-expect-error This function is missing from the Mocha type definitions - it.each([...UNSUPPORTED_RPC_METHODS.keys()])( - 'ends requests for methods that are on the list of unsupported methods: %s', + it.each(UNSUPPORTED_RPC_METHODS)( + 'ends requests for methods that are in the list of unsupported methods: %s', (method: string) => { - const middleware = createUnsupportedMethodMiddleware(); + const middleware = createUnsupportedMethodMiddleware( + UNSUPPORTED_RPC_METHODS, + ); const nextMock = jest.fn(); const endMock = jest.fn(); diff --git a/app/scripts/lib/rpc-method-middleware/createUnsupportedMethodMiddleware.ts b/app/scripts/lib/rpc-method-middleware/createUnsupportedMethodMiddleware.ts index c96201041d36..0ddf70268e10 100644 --- a/app/scripts/lib/rpc-method-middleware/createUnsupportedMethodMiddleware.ts +++ b/app/scripts/lib/rpc-method-middleware/createUnsupportedMethodMiddleware.ts @@ -1,18 +1,18 @@ import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; import type { JsonRpcParams } from '@metamask/utils'; import { rpcErrors } from '@metamask/rpc-errors'; -import { UNSUPPORTED_RPC_METHODS } from '../../../../shared/constants/network'; /** * Creates a middleware that rejects explicitly unsupported RPC methods with the * appropriate error. + * + * @param methods */ -export function createUnsupportedMethodMiddleware(): JsonRpcMiddleware< - JsonRpcParams, - null -> { +export function createUnsupportedMethodMiddleware( + methods: string[], +): JsonRpcMiddleware { return async function unsupportedMethodMiddleware(req, _res, next, end) { - if ((UNSUPPORTED_RPC_METHODS as Set).has(req.method)) { + if (methods.includes(req.method)) { return end(rpcErrors.methodNotSupported()); } return next(); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/handler.test.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/handler.test.ts new file mode 100644 index 000000000000..d5374c1d7e31 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/handler.test.ts @@ -0,0 +1,704 @@ +import { JsonRpcError } from '@metamask/rpc-errors'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, + Caip25Authorization, + NormalizedScopesObject, +} from '@metamask/multichain'; +import * as Multichain from '@metamask/multichain'; +import { Json, JsonRpcRequest, JsonRpcSuccess } from '@metamask/utils'; +import { CaveatTypes } from '../../../../../../shared/constants/permissions'; +import * as Util from '../../../util'; +import { PermissionNames } from '../../../../controllers/permissions'; +import * as Helpers from './helpers'; +import { walletCreateSession } from './handler'; + +jest.mock('../../../util', () => ({ + ...jest.requireActual('../../../util'), + shouldEmitDappViewedEvent: jest.fn(), +})); +const MockUtil = jest.mocked(Util); + +jest.mock('@metamask/multichain', () => ({ + ...jest.requireActual('@metamask/multichain'), + validateAndNormalizeScopes: jest.fn(), + bucketScopes: jest.fn(), + getSessionScopes: jest.fn(), + filterScopeObjectsSupported: jest.fn(), +})); +const MockMultichain = jest.mocked(Multichain); + +jest.mock('./helpers', () => ({ + ...jest.requireActual('./helpers'), + validateAndAddEip3085: jest.fn(), + processScopedProperties: jest.fn(), +})); +const MockHelpers = jest.mocked(Helpers); + +const baseRequest = { + jsonrpc: '2.0' as const, + id: 0, + method: 'wallet_createSession', + origin: 'http://test.com', + params: { + requiredScopes: { + eip155: { + references: ['1', '137'], + methods: [ + 'eth_sendTransaction', + 'eth_signTransaction', + 'eth_sign', + 'get_balance', + 'personal_sign', + ], + notifications: ['accountsChanged', 'chainChanged'], + }, + }, + sessionProperties: { + expiry: 'date', + foo: 'bar', + }, + }, +}; + +const createMockedHandler = () => { + const next = jest.fn(); + const end = jest.fn(); + const requestPermissionApprovalForOrigin = jest.fn().mockResolvedValue({ + approvedAccounts: ['0x1', '0x2', '0x3', '0x4'], + approvedChainIds: ['0x1', '0x5'], + }); + const grantPermissions = jest.fn().mockResolvedValue(undefined); + const findNetworkClientIdByChainId = jest.fn().mockReturnValue('mainnet'); + const addNetwork = jest.fn().mockResolvedValue(undefined); + const removeNetwork = jest.fn(); + const sendMetrics = jest.fn(); + const metamaskState = { + permissionHistory: {}, + metaMetricsId: 'metaMetricsId', + accounts: { + '0x1': {}, + '0x2': {}, + '0x3': {}, + }, + }; + const listAccounts = jest.fn().mockReturnValue([]); + const response = { + jsonrpc: '2.0' as const, + id: 0, + } as unknown as JsonRpcSuccess<{ + sessionScopes: NormalizedScopesObject; + sessionProperties?: Record; + }>; + const handler = ( + request: JsonRpcRequest & { origin: string }, + ) => + walletCreateSession.implementation(request, response, next, end, { + findNetworkClientIdByChainId, + requestPermissionApprovalForOrigin, + grantPermissions, + addNetwork, + removeNetwork, + metamaskState, + sendMetrics, + listAccounts, + }); + + return { + response, + next, + end, + findNetworkClientIdByChainId, + requestPermissionApprovalForOrigin, + grantPermissions, + addNetwork, + removeNetwork, + metamaskState, + sendMetrics, + listAccounts, + handler, + }; +}; + +describe('wallet_createSession', () => { + beforeEach(() => { + MockMultichain.validateAndNormalizeScopes.mockReturnValue({ + normalizedRequiredScopes: {}, + normalizedOptionalScopes: {}, + }); + MockMultichain.bucketScopes.mockReturnValue({ + supportedScopes: {}, + supportableScopes: {}, + unsupportableScopes: {}, + }); + MockMultichain.getSessionScopes.mockReturnValue({}); + MockMultichain.filterScopeObjectsSupported.mockImplementation( + (scopesObject) => scopesObject, + ); + MockHelpers.processScopedProperties.mockReturnValue({}); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('throws an error when session properties is defined but empty', async () => { + const { handler, end } = createMockedHandler(); + await handler({ + ...baseRequest, + params: { + ...baseRequest.params, + sessionProperties: {}, + }, + }); + expect(end).toHaveBeenCalledWith( + new JsonRpcError(5302, 'Invalid sessionProperties requested'), + ); + }); + + it('processes the scopes', async () => { + const { handler } = createMockedHandler(); + await handler({ + ...baseRequest, + params: { + ...baseRequest.params, + optionalScopes: { + foo: { + methods: [], + notifications: [], + }, + }, + }, + }); + + expect(MockMultichain.validateAndNormalizeScopes).toHaveBeenCalledWith( + baseRequest.params.requiredScopes, + { + foo: { + methods: [], + notifications: [], + }, + }, + ); + }); + + it('throws an error when processing scopes fails', async () => { + const { handler, end } = createMockedHandler(); + MockMultichain.validateAndNormalizeScopes.mockImplementation(() => { + throw new Error('failed to process scopes'); + }); + await handler(baseRequest); + expect(end).toHaveBeenCalledWith(new Error('failed to process scopes')); + }); + + it('processes the scopedProperties', async () => { + const { handler } = createMockedHandler(); + MockMultichain.validateAndNormalizeScopes.mockReturnValue({ + normalizedRequiredScopes: { + 'eip155:1': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + }, + normalizedOptionalScopes: { + 'eip155:100': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:100:0x4'], + }, + }, + }); + await handler({ + ...baseRequest, + params: { + ...baseRequest.params, + scopedProperties: { + foo: 'bar', + }, + }, + }); + + expect(MockHelpers.processScopedProperties).toHaveBeenCalledWith( + { + 'eip155:1': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + }, + { + 'eip155:100': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:100:0x4'], + }, + }, + { foo: 'bar' }, + ); + }); + + it('throws an error when processing scopedProperties fails', async () => { + const { handler, end } = createMockedHandler(); + MockHelpers.processScopedProperties.mockImplementation(() => { + throw new Error('failed to process scoped properties'); + }); + await handler(baseRequest); + expect(end).toHaveBeenCalledWith( + new Error('failed to process scoped properties'), + ); + }); + + it('filters the required scopesObjects', async () => { + const { handler } = createMockedHandler(); + MockMultichain.validateAndNormalizeScopes.mockReturnValue({ + normalizedRequiredScopes: { + 'eip155:1': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + }, + normalizedOptionalScopes: {}, + }); + await handler(baseRequest); + + expect(MockMultichain.filterScopeObjectsSupported).toHaveBeenNthCalledWith( + 1, + { + 'eip155:1': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + }, + ); + }); + + it('filters the optional scopesObjects', async () => { + const { handler } = createMockedHandler(); + MockMultichain.validateAndNormalizeScopes.mockReturnValue({ + normalizedRequiredScopes: {}, + normalizedOptionalScopes: { + 'eip155:1': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + }, + }); + await handler(baseRequest); + + expect(MockMultichain.filterScopeObjectsSupported).toHaveBeenNthCalledWith( + 2, + { + 'eip155:1': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + }, + ); + }); + + it('buckets the required scopes', async () => { + const { handler } = createMockedHandler(); + MockMultichain.validateAndNormalizeScopes.mockReturnValue({ + normalizedRequiredScopes: { + 'eip155:1': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + }, + normalizedOptionalScopes: {}, + }); + await handler(baseRequest); + + expect(MockMultichain.bucketScopes).toHaveBeenNthCalledWith( + 1, + { + 'eip155:1': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + }, + expect.objectContaining({ + isChainIdSupported: expect.any(Function), + isChainIdSupportable: expect.any(Function), + }), + ); + + const isChainIdSupportedBody = + MockMultichain.bucketScopes.mock.calls[0][1].isChainIdSupported.toString(); + expect(isChainIdSupportedBody).toContain('findNetworkClientIdByChainId'); + const isChainIdSupportableBody = + MockMultichain.bucketScopes.mock.calls[0][1].isChainIdSupportable.toString(); + expect(isChainIdSupportableBody).toContain('validScopedProperties'); + }); + + it('buckets the optional scopes', async () => { + const { handler } = createMockedHandler(); + MockMultichain.validateAndNormalizeScopes.mockReturnValue({ + normalizedRequiredScopes: {}, + normalizedOptionalScopes: { + 'eip155:100': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:100:0x4'], + }, + }, + }); + await handler(baseRequest); + + expect(MockMultichain.bucketScopes).toHaveBeenNthCalledWith( + 2, + { + 'eip155:100': { + methods: ['eth_chainId'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:100:0x4'], + }, + }, + expect.objectContaining({ + isChainIdSupported: expect.any(Function), + isChainIdSupportable: expect.any(Function), + }), + ); + + const isChainIdSupportedBody = + MockMultichain.bucketScopes.mock.calls[1][1].isChainIdSupported.toString(); + expect(isChainIdSupportedBody).toContain('findNetworkClientIdByChainId'); + const isChainIdSupportableBody = + MockMultichain.bucketScopes.mock.calls[1][1].isChainIdSupportable.toString(); + expect(isChainIdSupportableBody).toContain('validScopedProperties'); + }); + + it('gets a list of evm accounts in the wallet', async () => { + const { handler, listAccounts } = createMockedHandler(); + await handler(baseRequest); + + expect(listAccounts).toHaveBeenCalled(); + }); + + it('requests approval for account and permitted chains permission based on the supported eth accounts and eth chains from the supported scopes in the request', async () => { + const { handler, listAccounts, requestPermissionApprovalForOrigin } = + createMockedHandler(); + listAccounts.mockReturnValue([ + { address: '0x1' }, + { address: '0x3' }, + { address: '0x4' }, + ]); + MockMultichain.bucketScopes + .mockReturnValueOnce({ + supportedScopes: { + 'eip155:1337': { + methods: [], + notifications: [], + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + }, + supportableScopes: {}, + unsupportableScopes: {}, + }) + .mockReturnValueOnce({ + supportedScopes: { + 'eip155:100': { + methods: [], + notifications: [], + accounts: ['eip155:2:0x1', 'eip155:2:0x3', 'eip155:2:0xdeadbeef'], + }, + }, + supportableScopes: {}, + unsupportableScopes: {}, + }); + await handler(baseRequest); + + expect(requestPermissionApprovalForOrigin).toHaveBeenCalledWith({ + [PermissionNames.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0x1', '0x3'], + }, + ], + }, + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x539', '0x64'], + }, + ], + }, + }); + }); + + it('throws an error when requesting account permission approval fails', async () => { + const { handler, requestPermissionApprovalForOrigin, end } = + createMockedHandler(); + requestPermissionApprovalForOrigin.mockImplementation(() => { + throw new Error('failed to request account permission approval'); + }); + await handler(baseRequest); + expect(end).toHaveBeenCalledWith( + new Error('failed to request account permission approval'), + ); + }); + + it('validates and upserts EIP 3085 scoped properties when matching sessionScope is defined', async () => { + const { handler, findNetworkClientIdByChainId, addNetwork } = + createMockedHandler(); + MockHelpers.processScopedProperties.mockReturnValue({ + 'eip155:1': { + eip3085: { + foo: 'bar', + }, + }, + }); + MockMultichain.getSessionScopes.mockReturnValue({ + 'eip155:1': { + methods: [], + notifications: [], + accounts: ['eip155:1:0x1'], + }, + }); + await handler({ + ...baseRequest, + params: { + ...baseRequest.params, + scopedProperties: { + 'eip155:1': { + eip3085: { + foo: 'bar', + }, + }, + }, + }, + }); + + expect(MockHelpers.validateAndAddEip3085).toHaveBeenCalledWith({ + eip3085Params: { foo: 'bar' }, + addNetwork, + findNetworkClientIdByChainId, + }); + }); + + it('does not validate and upsert EIP 3085 scoped properties when there is no matching sessionScope', async () => { + const { handler } = createMockedHandler(); + MockMultichain.bucketScopes + .mockReturnValueOnce({ + supportedScopes: { + 'eip155:1': { + methods: [], + notifications: [], + accounts: ['eip155:1:0x1'], + }, + }, + supportableScopes: {}, + unsupportableScopes: {}, + }) + .mockReturnValueOnce({ + supportedScopes: {}, + supportableScopes: {}, + unsupportableScopes: {}, + }); + await handler({ + ...baseRequest, + params: { + ...baseRequest.params, + scopedProperties: { + 'eip155:99999': { + eip3085: { + foo: 'bar', + }, + }, + }, + }, + }); + + expect(MockHelpers.validateAndAddEip3085).not.toHaveBeenCalled(); + }); + + it('grants the CAIP-25 permission for the supported scopes and accounts that were approved', async () => { + const { handler, grantPermissions, requestPermissionApprovalForOrigin } = + createMockedHandler(); + MockMultichain.bucketScopes + .mockReturnValueOnce({ + supportedScopes: { + 'eip155:5': { + methods: ['eth_chainId'], + notifications: ['accountsChanged'], + accounts: [], + }, + }, + supportableScopes: {}, + unsupportableScopes: {}, + }) + .mockReturnValueOnce({ + supportedScopes: { + 'eip155:100': { + methods: ['eth_sendTransaction'], + notifications: ['chainChanged'], + accounts: ['eip155:1:0x3'], + }, + }, + supportableScopes: {}, + unsupportableScopes: {}, + }); + requestPermissionApprovalForOrigin.mockResolvedValue({ + approvedAccounts: ['0x1', '0x2'], + approvedChainIds: ['0x5', '0x64', '0x539'], // 5, 100, 1337 + }); + await handler(baseRequest); + + expect(grantPermissions).toHaveBeenCalledWith({ + subject: { origin: 'http://test.com' }, + approvedPermissions: { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: { + 'eip155:5': { + accounts: ['eip155:5:0x1', 'eip155:5:0x2'], + }, + }, + optionalScopes: { + 'eip155:100': { + accounts: ['eip155:100:0x1', 'eip155:100:0x2'], + }, + 'eip155:1337': { + accounts: ['eip155:1337:0x1', 'eip155:1337:0x2'], + }, + }, + isMultichainOrigin: true, + }, + }, + ], + }, + }, + }); + }); + + it('throws an error when granting the CAIP-25 permission fails', async () => { + const { handler, grantPermissions, end } = createMockedHandler(); + grantPermissions.mockImplementation(() => { + throw new Error('failed to grant CAIP-25 permissions'); + }); + await handler(baseRequest); + expect(end).toHaveBeenCalledWith( + new Error('failed to grant CAIP-25 permissions'), + ); + }); + + it('emits the dapp viewed metrics event', async () => { + MockUtil.shouldEmitDappViewedEvent.mockReturnValue(true); + const { handler, sendMetrics } = createMockedHandler(); + MockMultichain.bucketScopes.mockReturnValue({ + supportedScopes: {}, + supportableScopes: {}, + unsupportableScopes: {}, + }); + await handler(baseRequest); + + expect(sendMetrics).toHaveBeenCalledWith({ + category: 'inpage_provider', + event: 'Dapp Viewed', + properties: { + is_first_visit: true, + number_of_accounts: 3, + number_of_accounts_connected: 4, + }, + referrer: { + url: 'http://test.com', + }, + }); + }); + + it('returns the session ID, properties, and session scopes', async () => { + const { handler, response } = createMockedHandler(); + MockMultichain.getSessionScopes.mockReturnValue({ + 'eip155:5': { + methods: ['eth_chainId', 'net_version'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:5:0x1', 'eip155:5:0x2'], + }, + 'eip155:100': { + methods: ['eth_sendTransaction'], + notifications: ['chainChanged'], + accounts: ['eip155:100:0x1', 'eip155:100:0x2'], + }, + 'wallet:eip155': { + methods: [], + notifications: [], + accounts: ['wallet:eip155:0x1', 'wallet:eip155:0x2'], + }, + }); + await handler(baseRequest); + + expect(response.result).toStrictEqual({ + sessionProperties: { + expiry: 'date', + foo: 'bar', + }, + sessionScopes: { + 'eip155:5': { + methods: ['eth_chainId', 'net_version'], + notifications: ['accountsChanged', 'chainChanged'], + accounts: ['eip155:5:0x1', 'eip155:5:0x2'], + }, + 'eip155:100': { + methods: ['eth_sendTransaction'], + notifications: ['chainChanged'], + accounts: ['eip155:100:0x1', 'eip155:100:0x2'], + }, + 'wallet:eip155': { + methods: [], + notifications: [], + accounts: ['wallet:eip155:0x1', 'wallet:eip155:0x2'], + }, + }, + }); + }); + + it('reverts any upserted network clients if the request fails', async () => { + const { handler, removeNetwork, grantPermissions } = createMockedHandler(); + MockMultichain.getSessionScopes.mockReturnValue({ + 'eip155:1': { + methods: [], + notifications: [], + accounts: [], + }, + }); + MockHelpers.processScopedProperties.mockReturnValue({ + 'eip155:1': { + eip3085: { + foo: 'bar', + }, + }, + }); + MockHelpers.validateAndAddEip3085.mockResolvedValue('0xdeadbeef'); + grantPermissions.mockImplementation(() => { + throw new Error('failed to grant permission'); + }); + + await handler({ + ...baseRequest, + params: { + ...baseRequest.params, + scopedProperties: { + 'eip155:1': { + eip3085: { + foo: 'bar', + }, + }, + }, + }, + }); + + expect(removeNetwork).toHaveBeenCalledWith('0xdeadbeef'); + }); +}); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/handler.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/handler.ts new file mode 100644 index 000000000000..5625b2047dbc --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/handler.ts @@ -0,0 +1,324 @@ +import { JsonRpcError } from '@metamask/rpc-errors'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, + getEthAccounts, + setEthAccounts, + getPermittedEthChainIds, + setPermittedEthChainIds, + bucketScopes, + validateAndNormalizeScopes, + Caip25Authorization, + ScopedProperties, + getInternalScopesObject, + getSessionScopes, + NormalizedScopesObject, + InternalScopeString, + filterScopeObjectsSupported, +} from '@metamask/multichain'; +import { + Caveat, + CaveatSpecificationConstraint, + invalidParams, + PermissionController, + PermissionSpecificationConstraint, + RequestedPermissions, + ValidPermission, +} from '@metamask/permission-controller'; +import { + CaipChainId, + Hex, + isPlainObject, + Json, + JsonRpcRequest, + JsonRpcSuccess, +} from '@metamask/utils'; +import { NetworkController } from '@metamask/network-controller'; +import { + JsonRpcEngineEndCallback, + JsonRpcEngineNextCallback, +} from '@metamask/json-rpc-engine'; +import { PermissionNames } from '../../../../controllers/permissions'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, + MetaMetricsEventOptions, + MetaMetricsEventPayload, +} from '../../../../../../shared/constants/metametrics'; +import { shouldEmitDappViewedEvent } from '../../../util'; +import { CaveatTypes } from '../../../../../../shared/constants/permissions'; +import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; +import { processScopedProperties, validateAndAddEip3085 } from './helpers'; + +type AbstractPermissionController = PermissionController< + PermissionSpecificationConstraint, + CaveatSpecificationConstraint +>; + +/** + * Handler for the `wallet_createSession` RPC method. + * + * @param req - The request object. + * @param res - The response object. + * @param _next - The next middleware function. + * @param end - The end function. + * @param hooks - The hooks object. + * @param hooks.listAccounts + * @param hooks.removeNetwork + * @param hooks.addNetwork + * @param hooks.findNetworkClientIdByChainId + * @param hooks.requestPermissionApprovalForOrigin + * @param hooks.sendMetrics + * @param hooks.metamaskState + * @param hooks.metamaskState.metaMetricsId + * @param hooks.metamaskState.permissionHistory + * @param hooks.metamaskState.accounts + * @param hooks.grantPermissions + */ +async function walletCreateSessionHandler( + req: JsonRpcRequest & { origin: string }, + res: JsonRpcSuccess<{ + sessionScopes: NormalizedScopesObject; + sessionProperties?: Record; + }>, + _next: JsonRpcEngineNextCallback, + end: JsonRpcEngineEndCallback, + hooks: { + listAccounts: () => { address: string }[]; + removeNetwork: NetworkController['removeNetwork']; + addNetwork: NetworkController['addNetwork']; + findNetworkClientIdByChainId: NetworkController['findNetworkClientIdByChainId']; + requestPermissionApprovalForOrigin: ( + requestedPermissions: RequestedPermissions, + ) => Promise<{ approvedAccounts: Hex[]; approvedChainIds: Hex[] }>; + sendMetrics: ( + payload: MetaMetricsEventPayload, + options?: MetaMetricsEventOptions, + ) => void; + metamaskState: { + metaMetricsId: string; + permissionHistory: Record; + accounts: Record; + }; + grantPermissions: ( + ...args: Parameters + ) => Record>>; + }, +) { + const { origin } = req; + if (!isPlainObject(req.params)) { + return end(invalidParams({ data: { request: req } })); + } + const { + requiredScopes, + optionalScopes, + sessionProperties, + scopedProperties, + } = req.params; + + if (sessionProperties && Object.keys(sessionProperties).length === 0) { + return end(new JsonRpcError(5302, 'Invalid sessionProperties requested')); + } + + const chainIdsForNetworksAdded: Hex[] = []; + + try { + const { normalizedRequiredScopes, normalizedOptionalScopes } = + validateAndNormalizeScopes(requiredScopes || {}, optionalScopes || {}); + + const validScopedProperties = processScopedProperties( + normalizedRequiredScopes, + normalizedOptionalScopes, + scopedProperties as ScopedProperties, + ); + + const supportedRequiredScopesObjects = filterScopeObjectsSupported( + normalizedRequiredScopes, + ); + const supportedOptionalScopesObjects = filterScopeObjectsSupported( + normalizedOptionalScopes, + ); + + const existsNetworkClientForChainId = (chainId: Hex) => { + try { + hooks.findNetworkClientIdByChainId(chainId); + return true; + } catch (err) { + return false; + } + }; + + const existsEip3085ForChainId = (chainId: Hex) => { + const scopeString: CaipChainId = `eip155:${parseInt(chainId, 16)}`; + return Boolean(validScopedProperties?.[scopeString]?.eip3085); + }; + + const { + supportedScopes: supportedRequiredScopes, + supportableScopes: supportableRequiredScopes, + unsupportableScopes: unsupportableRequiredScopes, + } = bucketScopes(supportedRequiredScopesObjects, { + isChainIdSupported: existsNetworkClientForChainId, + isChainIdSupportable: existsEip3085ForChainId, + }); + + const { + supportedScopes: supportedOptionalScopes, + supportableScopes: supportableOptionalScopes, + unsupportableScopes: unsupportableOptionalScopes, + } = bucketScopes(supportedOptionalScopesObjects, { + isChainIdSupported: existsNetworkClientForChainId, + isChainIdSupportable: existsEip3085ForChainId, + }); + + // TODO: placeholder for future CAIP-25 permission confirmation call + JSON.stringify({ + supportedRequiredScopes, + supportableRequiredScopes, + unsupportableRequiredScopes, + supportedOptionalScopes, + supportableOptionalScopes, + unsupportableOptionalScopes, + }); + + // Fetch EVM accounts from native wallet keyring + // These addresses are lowercased already + const existingEvmAddresses = hooks + .listAccounts() + .map((account) => account.address); + const supportedEthAccounts = getEthAccounts({ + requiredScopes: supportedRequiredScopes, + optionalScopes: supportedOptionalScopes, + }) + .map((address) => address.toLowerCase()) + .filter((address) => existingEvmAddresses.includes(address)); + const supportedEthChainIds = getPermittedEthChainIds({ + requiredScopes: supportedRequiredScopes, + optionalScopes: supportedOptionalScopes, + }); + + const legacyApproval = await hooks.requestPermissionApprovalForOrigin({ + [PermissionNames.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: supportedEthAccounts, + }, + ], + }, + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: supportedEthChainIds, + }, + ], + }, + }); + + let caip25CaveatValue = { + requiredScopes: getInternalScopesObject(supportedRequiredScopes), + optionalScopes: getInternalScopesObject(supportedOptionalScopes), + isMultichainOrigin: true, + // NOTE: We aren't persisting sessionProperties from the CAIP-25 + // request because we don't do anything with it yet. + }; + + caip25CaveatValue = setPermittedEthChainIds( + caip25CaveatValue, + legacyApproval.approvedChainIds, + ); + caip25CaveatValue = setEthAccounts( + caip25CaveatValue, + legacyApproval.approvedAccounts, + ); + + const sessionScopes = getSessionScopes(caip25CaveatValue); + + await Promise.all( + Object.entries(validScopedProperties).map( + async ([scopeString, scopedProperty]) => { + const scope = sessionScopes[scopeString as InternalScopeString]; + if (!scope) { + return; + } + + const chainId = await validateAndAddEip3085({ + eip3085Params: scopedProperty.eip3085, + addNetwork: hooks.addNetwork, + findNetworkClientIdByChainId: hooks.findNetworkClientIdByChainId, + }); + + if (chainId) { + chainIdsForNetworksAdded.push(chainId); + } + }, + ), + ); + + hooks.grantPermissions({ + subject: { + origin, + }, + approvedPermissions: { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: caip25CaveatValue, + }, + ], + }, + }, + }); + + // TODO: Contact analytics team for how they would prefer to track this + // first time connection to dapp will lead to no log in the permissionHistory + // and if user has connected to dapp before, the dapp origin will be included in the permissionHistory state + // we will leverage that to identify `is_first_visit` for metrics + if (shouldEmitDappViewedEvent(hooks.metamaskState.metaMetricsId)) { + const isFirstVisit = !Object.keys( + hooks.metamaskState.permissionHistory, + ).includes(origin); + + hooks.sendMetrics({ + event: MetaMetricsEventName.DappViewed, + category: MetaMetricsEventCategory.InpageProvider, + referrer: { + url: origin, + }, + properties: { + is_first_visit: isFirstVisit, + number_of_accounts: Object.keys(hooks.metamaskState.accounts).length, + number_of_accounts_connected: legacyApproval.approvedAccounts.length, + }, + }); + } + + res.result = { + sessionScopes, + sessionProperties, + }; + return end(); + } catch (err) { + chainIdsForNetworksAdded.forEach((chainId) => { + hooks.removeNetwork(chainId); + }); + return end(err); + } +} + +export const walletCreateSession = { + methodNames: [MESSAGE_TYPE.WALLET_CREATE_SESSION], + implementation: walletCreateSessionHandler, + hookNames: { + removeNetwork: true, + findNetworkClientIdByChainId: true, + listAccounts: true, + addNetwork: true, + requestPermissionApprovalForOrigin: true, + grantPermissions: true, + sendMetrics: true, + metamaskState: true, + }, +}; diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/helpers.test.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/helpers.test.ts new file mode 100644 index 000000000000..a5a067994759 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/helpers.test.ts @@ -0,0 +1,308 @@ +import { RpcEndpointType } from '@metamask/network-controller'; +import { NormalizedScopeObject } from '@metamask/multichain'; +import * as EthereumChainUtils from '../ethereum-chain-utils'; +import { + validateAndAddEip3085, + validateScopedPropertyEip3085, + processScopedProperties, +} from './helpers'; + +const validScopeObject: NormalizedScopeObject = { + methods: [], + notifications: [], + accounts: [], +}; + +jest.mock('../ethereum-chain-utils', () => ({ + validateAddEthereumChainParams: jest.fn(), +})); +const MockEthereumChainUtils = jest.mocked(EthereumChainUtils); + +describe('wallet_createSession helpers', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('processScopedProperties', () => { + it('excludes scopeStrings that are not defined in either required or optional scopes', () => { + expect( + processScopedProperties( + { + 'eip155:1': validScopeObject, + }, + { + 'eip155:5': validScopeObject, + }, + { + 'eip155:10': {}, + }, + { + validateScopedPropertyEip3085: jest.fn(), + }, + ), + ).toStrictEqual({}); + }); + + it('includes scopeStrings that are defined in either required or optional scopes', () => { + expect( + processScopedProperties( + { + 'eip155:1': validScopeObject, + }, + { + 'eip155:5': validScopeObject, + }, + { + 'eip155:1': {}, + 'eip155:5': {}, + }, + { + validateScopedPropertyEip3085: jest.fn(), + }, + ), + ).toStrictEqual({ + 'eip155:1': {}, + 'eip155:5': {}, + }); + }); + + it('validates eip3085 properties', () => { + const mockValidateScopedPropertyEip3085 = jest.fn(); + processScopedProperties( + { + 'eip155:1': validScopeObject, + }, + {}, + { + 'eip155:1': { + eip3085: { + foo: 'bar', + }, + }, + }, + { + validateScopedPropertyEip3085: mockValidateScopedPropertyEip3085, + }, + ); + expect(mockValidateScopedPropertyEip3085).toHaveBeenCalledWith( + 'eip155:1', + { + foo: 'bar', + }, + ); + }); + + it('excludes invalid eip3085 properties', () => { + const mockValidateScopedPropertyEip3085 = jest + .fn() + .mockImplementation(() => { + throw new Error('invalid eip3085 params'); + }); + expect( + processScopedProperties( + { + 'eip155:1': validScopeObject, + }, + {}, + { + 'eip155:1': { + eip3085: { + foo: 'bar', + }, + }, + }, + { + validateScopedPropertyEip3085: mockValidateScopedPropertyEip3085, + }, + ), + ).toStrictEqual({ + 'eip155:1': {}, + }); + }); + + it('includes valid eip3085 properties', () => { + expect( + processScopedProperties( + { + 'eip155:1': validScopeObject, + }, + {}, + { + 'eip155:1': { + eip3085: { + foo: 'bar', + }, + }, + }, + { + validateScopedPropertyEip3085: jest.fn(), + }, + ), + ).toStrictEqual({ + 'eip155:1': { + eip3085: { + foo: 'bar', + }, + }, + }); + }); + }); + + describe('validateScopedPropertyEip3085', () => { + it('throws an error if eip3085 params are not provided', () => { + expect(() => validateScopedPropertyEip3085('', undefined)).toThrow( + new Error('eip3085 params are missing'), + ); + }); + + it('throws an error if the scopeString is not a CAIP chain ID', () => { + expect(() => validateScopedPropertyEip3085('eip155', {})).toThrow( + new Error('scopeString is malformed'), + ); + }); + + it('throws an error if the namespace is not eip155', () => { + expect(() => validateScopedPropertyEip3085('wallet:1', {})).toThrow( + new Error('namespace is not eip155'), + ); + }); + + it('validates the 3085 params', () => { + try { + validateScopedPropertyEip3085('eip155:1', { foo: 'bar' }); + } catch (err) { + // noop + } + expect( + MockEthereumChainUtils.validateAddEthereumChainParams, + ).toHaveBeenCalledWith({ foo: 'bar' }); + }); + + it('throws an error if the 3085 params are invalid', () => { + MockEthereumChainUtils.validateAddEthereumChainParams.mockImplementation( + () => { + throw new Error('invalid eth chain params'); + }, + ); + expect(() => + validateScopedPropertyEip3085('eip155:1', { foo: 'bar' }), + ).toThrow(new Error('invalid eth chain params')); + }); + + it('throws an error if the 3085 params chainId does not match the reference', () => { + MockEthereumChainUtils.validateAddEthereumChainParams.mockReturnValue({ + chainId: '0x5', + chainName: 'test', + firstValidBlockExplorerUrl: 'http://explorer.test.com', + firstValidRPCUrl: 'http://rpc.test.com', + ticker: 'TST', + }); + expect(() => + validateScopedPropertyEip3085('eip155:1', { foo: 'bar' }), + ).toThrow(new Error('eip3085 chainId does not match reference')); + }); + it('returns the validated 3085 params when valid', () => { + MockEthereumChainUtils.validateAddEthereumChainParams.mockReturnValue({ + chainId: '0x1', + chainName: 'test', + firstValidBlockExplorerUrl: 'http://explorer.test.com', + firstValidRPCUrl: 'http://rpc.test.com', + ticker: 'TST', + }); + expect( + validateScopedPropertyEip3085('eip155:1', { foo: 'bar' }), + ).toStrictEqual({ + chainId: '0x1', + chainName: 'test', + firstValidBlockExplorerUrl: 'http://explorer.test.com', + firstValidRPCUrl: 'http://rpc.test.com', + ticker: 'TST', + }); + }); + }); + + describe('validateAndAddEip3085', () => { + const addNetwork = jest.fn(); + const findNetworkClientIdByChainId = jest.fn(); + + beforeEach(() => { + findNetworkClientIdByChainId.mockImplementation(() => { + throw new Error('cannot find network client for chainId'); + }); + + MockEthereumChainUtils.validateAddEthereumChainParams.mockReturnValue({ + chainId: '0x5', + chainName: 'test', + firstValidBlockExplorerUrl: 'http://explorer.test.com', + firstValidRPCUrl: 'http://rpc.test.com', + ticker: 'TST', + }); + }); + + it('validates the eip3085 params', async () => { + try { + await validateAndAddEip3085({ + eip3085Params: { foo: 'bar' }, + addNetwork, + findNetworkClientIdByChainId, + }); + } catch (err) { + // noop + } + expect( + MockEthereumChainUtils.validateAddEthereumChainParams, + ).toHaveBeenCalledWith({ foo: 'bar' }); + }); + + it('checks if the chainId can already be served', async () => { + try { + await validateAndAddEip3085({ + eip3085Params: { foo: 'bar' }, + addNetwork, + findNetworkClientIdByChainId, + }); + } catch (err) { + // noop + } + expect(findNetworkClientIdByChainId).toHaveBeenCalledWith('0x5'); + }); + + it('returns undefined if a network client already exists for the chainId', async () => { + findNetworkClientIdByChainId.mockReturnValue('existingNetworkClientId'); + const result = await validateAndAddEip3085({ + eip3085Params: {}, + addNetwork, + findNetworkClientIdByChainId, + }); + + expect(addNetwork).not.toHaveBeenCalled(); + expect(result).toStrictEqual(undefined); + }); + + it('adds a new network returns the chainId if a network client does not already exist for the chainId', async () => { + addNetwork.mockResolvedValue({ chainId: '0x5' }); + const result = await validateAndAddEip3085({ + eip3085Params: {}, + addNetwork, + findNetworkClientIdByChainId, + }); + + expect(addNetwork).toHaveBeenCalledWith({ + blockExplorerUrls: ['http://explorer.test.com'], + defaultBlockExplorerUrlIndex: 0, + chainId: '0x5', + defaultRpcEndpointIndex: 0, + name: 'test', + nativeCurrency: 'TST', + rpcEndpoints: [ + { + url: 'http://rpc.test.com', + name: 'test', + type: RpcEndpointType.Custom, + }, + ], + }); + expect(result).toStrictEqual('0x5'); + }); + }); +}); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/helpers.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/helpers.ts new file mode 100644 index 000000000000..45af96c213de --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/helpers.ts @@ -0,0 +1,125 @@ +import { CaipChainId, Hex, KnownCaipNamespace } from '@metamask/utils'; +import { + NetworkController, + RpcEndpointType, +} from '@metamask/network-controller'; +import { + parseScopeString, + ScopedProperties, + NormalizedScopesObject, +} from '@metamask/multichain'; +import { toHex } from '@metamask/controller-utils'; +import { validateAddEthereumChainParams } from '../ethereum-chain-utils'; + +export const validateScopedPropertyEip3085 = ( + scopeString: string, + eip3085Params: unknown, +) => { + if (!eip3085Params) { + throw new Error('eip3085 params are missing'); + } + + const { namespace, reference } = parseScopeString(scopeString); + + if (!namespace || !reference) { + throw new Error('scopeString is malformed'); + } + + if (namespace !== KnownCaipNamespace.Eip155) { + throw new Error('namespace is not eip155'); + } + + const validParams = validateAddEthereumChainParams(eip3085Params); + + if (validParams.chainId !== toHex(reference)) { + throw new Error('eip3085 chainId does not match reference'); + } + + return validParams; +}; + +export const validateAndAddEip3085 = async ({ + eip3085Params, + addNetwork, + findNetworkClientIdByChainId, +}: { + eip3085Params: unknown; + addNetwork: NetworkController['addNetwork']; + findNetworkClientIdByChainId: NetworkController['findNetworkClientIdByChainId']; +}): Promise => { + const validParams = validateAddEthereumChainParams(eip3085Params); + + const { + chainId, + chainName, + firstValidBlockExplorerUrl, + firstValidRPCUrl, + ticker, + } = validParams; + + try { + findNetworkClientIdByChainId(chainId as Hex); + return undefined; + } catch (err) { + // noop + } + + const networkConfiguration = await addNetwork({ + blockExplorerUrls: firstValidBlockExplorerUrl + ? [firstValidBlockExplorerUrl] + : [], + defaultBlockExplorerUrlIndex: firstValidBlockExplorerUrl ? 0 : undefined, + chainId: chainId as Hex, + defaultRpcEndpointIndex: 0, + name: chainName, + nativeCurrency: ticker, + rpcEndpoints: [ + { + url: firstValidRPCUrl, + name: chainName, + type: RpcEndpointType.Custom, + }, + ], + }); + + return networkConfiguration.chainId; +}; + +export const processScopedProperties = ( + requiredScopes: NormalizedScopesObject, + optionalScopes: NormalizedScopesObject, + scopedProperties?: ScopedProperties, + hooks = { validateScopedPropertyEip3085 }, +): ScopedProperties => { + if (!scopedProperties) { + return {}; + } + const validScopedProperties: ScopedProperties = {}; + + for (const [scopeString, scopedProperty] of Object.entries( + scopedProperties, + )) { + const scope = + requiredScopes[scopeString as CaipChainId] || + optionalScopes[scopeString as CaipChainId]; + if (!scope) { + continue; + } + validScopedProperties[scopeString as CaipChainId] = {}; + + if (scopedProperty.eip3085) { + try { + hooks.validateScopedPropertyEip3085( + scopeString, + scopedProperty.eip3085, + ); + validScopedProperties[scopeString as CaipChainId].eip3085 = + scopedProperty.eip3085; + } catch (err) { + // noop + } + } + } + + return validScopedProperties; +}; diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/index.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/index.ts new file mode 100644 index 000000000000..68ae53f6c3d8 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-createSession/index.ts @@ -0,0 +1 @@ +export * from './handler'; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index db6aa7e4e0e6..0c8f1d6258d2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -153,7 +153,6 @@ import { import { Interface } from '@ethersproject/abi'; import { abiERC1155, abiERC721 } from '@metamask/metamask-eth-abis'; import { isEvmAccountType } from '@metamask/keyring-api'; -import { toCaipChainId } from '@metamask/utils'; import { AuthenticationController, UserStorageController, @@ -167,9 +166,19 @@ import { Caip25CaveatType, Caip25EndowmentPermissionName, getEthAccounts, + getSessionScopes, setPermittedEthChainIds, setEthAccounts, addPermittedEthChainId, + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + multichainMethodCallValidatorMiddleware, + MultichainSubscriptionManager, + MultichainMiddlewareManager, + walletGetSession, + walletRevokeSession, + walletInvokeMethod, + caipPermissionAdapterMiddleware, + ///: END:ONLY_INCLUDE_IF } from '@metamask/multichain'; import { methodsRequiringNetworkSwitch, @@ -192,6 +201,7 @@ import { CHAIN_SPEC_URL, NETWORK_TYPES, NetworkStatus, + UNSUPPORTED_RPC_METHODS, MAINNET_DISPLAY_NAME, } from '../../shared/constants/network'; import { getAllowedSmartTransactionsChainIds } from '../../shared/constants/smartTransactions'; @@ -213,6 +223,9 @@ import { MILLISECOND, MINUTE, SECOND } from '../../shared/constants/time'; import { ORIGIN_METAMASK, POLLING_TOKEN_ENVIRONMENT_TYPES, + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + MESSAGE_TYPE, + ///: END:ONLY_INCLUDE_IF } from '../../shared/constants/app'; import { MetaMetricsEventCategory, @@ -244,7 +257,9 @@ import { getHardwareWalletType, getSmartTransactionsPreferenceEnabled, } from '../../shared/modules/selectors'; +///: BEGIN:ONLY_INCLUDE_IF(build-flask) import { createCaipStream } from '../../shared/modules/caip-stream'; +///: END:ONLY_INCLUDE_IF import { BaseUrl } from '../../shared/constants/urls'; import { TOKEN_TRANSFER_LOG_TOPIC_HASH, @@ -310,6 +325,10 @@ import { createEthAccountsMethodMiddleware, createEip1193MethodMiddleware, createUnsupportedMethodMiddleware, + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + createMultichainMethodMiddleware, + makeMethodMiddlewareMaker, + ///: END:ONLY_INCLUDE_IF } from './lib/rpc-method-middleware'; import createOriginMiddleware from './lib/createOriginMiddleware'; import createMainFrameOriginMiddleware from './lib/createMainFrameOriginMiddleware'; @@ -348,6 +367,11 @@ import { NOTIFICATION_NAMES, unrestrictedMethods, PermissionNames, + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + getRemovedAuthorizations, + getChangedAuthorizations, + getAuthorizedScopesByOrigin, + ///: END:ONLY_INCLUDE_IF } from './controllers/permissions'; import { MetaMetricsDataDeletionController } from './controllers/metametrics-data-deletion/metametrics-data-deletion'; import { DataDeletionService } from './services/data-deletion-service'; @@ -374,6 +398,7 @@ import { createTxVerificationMiddleware } from './lib/tx-verification/tx-verific import { updateSecurityAlertResponse } from './lib/ppom/ppom-util'; import createEvmMethodsToNonEvmAccountReqFilterMiddleware from './lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware'; import { isEthAddress } from './lib/multichain/address'; + import { decodeTransactionData } from './lib/transaction/decode/util'; import BridgeController from './controllers/bridge/bridge-controller'; import { BRIDGE_CONTROLLER_NAME } from './controllers/bridge/constants'; @@ -384,6 +409,9 @@ import { import createTracingMiddleware from './lib/createTracingMiddleware'; import { PatchStore } from './lib/PatchStore'; import { sanitizeUIState } from './lib/state-utils'; +///: BEGIN:ONLY_INCLUDE_IF(build-flask) +import { walletCreateSession } from './lib/rpc-method-middleware/handlers/wallet-createSession'; +///: END:ONLY_INCLUDE_IF import BridgeStatusController from './controllers/bridge-status/bridge-status-controller'; import { BRIDGE_STATUS_CONTROLLER_NAME } from './controllers/bridge-status/constants'; import { rejectAllApprovals } from './lib/approval/utils'; @@ -403,6 +431,12 @@ export const METAMASK_CONTROLLER_EVENTS = { 'NotificationServicesController:markNotificationsAsRead', }; +// Types of APIs +const API_TYPE = { + EIP1193: 'eip-1193', + CAIP_MULTICHAIN: 'caip-multichain', +}; + // stream channels const PHISHING_SAFELIST = 'metamask-phishing-safelist'; @@ -624,6 +658,19 @@ export default class MetamaskController extends EventEmitter { infuraProjectId: opts.infuraProjectId, }); this.networkController.initializeProvider(); + + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + this.multichainSubscriptionManager = new MultichainSubscriptionManager({ + getNetworkClientById: this.networkController.getNetworkClientById.bind( + this.networkController, + ), + findNetworkClientIdByChainId: + this.networkController.findNetworkClientIdByChainId.bind( + this.networkController, + ), + }); + this.multichainMiddlewareManager = new MultichainMiddlewareManager(); + ///: END:ONLY_INCLUDE_IF this.provider = this.networkController.getProviderAndBlockTracker().provider; this.blockTracker = @@ -3059,6 +3106,119 @@ export default class MetamaskController extends EventEmitter { getPermittedAccountsByOrigin, ); + // This handles CAIP-25 authorization changes every time relevant permission state + // changes, for any reason. + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + this.controllerMessenger.subscribe( + `${this.permissionController.name}:stateChange`, + async (currentValue, previousValue) => { + const changedAuthorizations = getChangedAuthorizations( + currentValue, + previousValue, + ); + + const removedAuthorizations = getRemovedAuthorizations( + currentValue, + previousValue, + ); + + // remove any existing notification subscriptions for removed authorizations + for (const [origin, authorization] of removedAuthorizations.entries()) { + const sessionScopes = getSessionScopes(authorization); + // if the eth_subscription notification is in the scope and eth_subscribe is in the methods + // then remove middleware and unsubscribe + Object.entries(sessionScopes).forEach(([scope, scopeObject]) => { + if ( + scopeObject.notifications.includes('eth_subscription') && + scopeObject.methods.includes('eth_subscribe') + ) { + this.multichainMiddlewareManager.removeMiddlewareByScopeAndOrigin( + scope, + origin, + ); + this.multichainSubscriptionManager.unsubscribeByScopeAndOrigin( + scope, + origin, + ); + } + }); + } + + // add new notification subscriptions for changed authorizations + for (const [origin, authorization] of changedAuthorizations.entries()) { + const sessionScopes = getSessionScopes(authorization); + + // if the eth_subscription notification is in the scope and eth_subscribe is in the methods + // then get the subscriptionManager going for that scope + Object.entries(sessionScopes).forEach(([scope, scopeObject]) => { + if ( + scopeObject.notifications.includes('eth_subscription') && + scopeObject.methods.includes('eth_subscribe') + ) { + // for each tabId + Object.entries(this.connections[origin]).forEach( + ([_, { tabId }]) => { + const subscriptionManager = + this.multichainSubscriptionManager.subscribe({ + scope, + origin, + tabId, + }); + this.multichainMiddlewareManager.addMiddleware({ + scope, + origin, + tabId, + middleware: subscriptionManager.middleware, + }); + }, + ); + } else { + this.multichainMiddlewareManager.removeMiddlewareByScopeAndOrigin( + scope, + origin, + ); + this.multichainSubscriptionManager.unsubscribeByScopeAndOrigin( + scope, + origin, + ); + } + }); + + // TODO: could be pushed into selectors? + const previousAuthorization = previousValue.get(origin); + if (previousAuthorization) { + const previousSessionScopes = getSessionScopes( + previousAuthorization, + ); + + Object.entries(previousSessionScopes).forEach( + ([scope, scopeObject]) => { + if (!sessionScopes[scope]) { + if ( + scopeObject.notifications.includes('eth_subscription') && + scopeObject.methods.includes('eth_subscribe') + ) { + this.multichainMiddlewareManager.removeMiddlewareByScopeAndOrigin( + scope, + origin, + ); + this.multichainSubscriptionManager.unsubscribeByScopeAndOrigin( + scope, + origin, + ); + } + } + }, + ); + } + + this._notifyAuthorizationChange(origin, authorization); + } + }, + getAuthorizedScopesByOrigin, + ); + ///: END:ONLY_INCLUDE_IF + this.controllerMessenger.subscribe( `${this.permissionController.name}:stateChange`, async (currentValue, previousValue) => { @@ -3321,16 +3481,21 @@ export default class MetamaskController extends EventEmitter { * Gets relevant state for the provider of an external origin. * * @param {string} origin - The origin to get the provider state for. - * @returns {Promise<{ isUnlocked: boolean, networkVersion: string, chainId: string, accounts: string[] }>} An object with relevant state properties. + * @returns {Promise<{ isUnlocked: boolean, networkVersion: string, chainId: string, accounts: string[], extensionId: string | undefined }>} An object with relevant state properties. */ async getProviderState(origin) { const providerNetworkState = await this.getProviderNetworkState( this.preferencesController.getUseRequestQueue() ? origin : undefined, ); - + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + const { chrome } = globalThis; + ///: END:ONLY_INCLUDE_IF return { isUnlocked: this.isUnlocked(), accounts: this.getPermittedAccounts(origin), + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + ...(isManifestV3 ? { extensionId: chrome?.runtime?.id } : {}), + ///: END:ONLY_INCLUDE_IF ...providerNetworkState, }; } @@ -5283,18 +5448,18 @@ export default class MetamaskController extends EventEmitter { } /** - * Stops exposing the specified chain ID to all third parties. + * Stops exposing the specified scope to all third parties. * - * @param {string} targetChainId - The chain ID to stop exposing + * @param {string} scopeString - The scope to stop exposing * to third parties. */ - removeAllChainIdPermissions(targetChainId) { + removeAllScopePermissions(scopeString) { this.permissionController.updatePermissionsByCaveat( Caip25CaveatType, (existingScopes) => Caip25CaveatMutators[Caip25CaveatType].removeScope( existingScopes, - toCaipChainId('eip155', parseInt(targetChainId, 16).toString()), + scopeString, ), ); } @@ -5828,7 +5993,7 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender | SnapSender} options.sender - The sender of the messages on this stream. * @param {string} [options.subjectType] - The type of the sender, i.e. subject. */ - + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) setupUntrustedCommunicationCaip({ connectionStream, sender, subjectType }) { let inputSubjectType; if (subjectType) { @@ -5844,6 +6009,7 @@ export default class MetamaskController extends EventEmitter { // messages between subject and background this.setupProviderConnectionCaip(caipStream, sender, inputSubjectType); } + ///: END:ONLY_INCLUDE_IF /** * Used to create a multiplexed stream for connecting to a trusted context, @@ -6091,7 +6257,11 @@ export default class MetamaskController extends EventEmitter { // setup connection const providerStream = createEngineStream({ engine }); - const connectionId = this.addConnection(origin, { engine }); + const connectionId = this.addConnection(origin, { + tabId, + apiType: API_TYPE.EIP1193, + engine, + }); pipeline( outStream, @@ -6122,6 +6292,7 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender | SnapSender} sender - The sender of the messages on this stream * @param {SubjectType} subjectType - The type of the sender, i.e. subject. */ + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) setupProviderConnectionCaip(outStream, sender, subjectType) { let origin; if (subjectType === SubjectType.Internal) { @@ -6155,7 +6326,11 @@ export default class MetamaskController extends EventEmitter { // setup connection const providerStream = createEngineStream({ engine }); - const connectionId = this.addConnection(origin, { engine }); + const connectionId = this.addConnection(origin, { + tabId, + apiType: API_TYPE.CAIP_MULTICHAIN, + engine, + }); pipeline( outStream, @@ -6164,23 +6339,16 @@ export default class MetamaskController extends EventEmitter { outStream, (err) => { // handle any middleware cleanup - engine._middleware.forEach((mid) => { - if (mid.destroy && typeof mid.destroy === 'function') { - mid.destroy(); - } - }); + engine.destroy(); connectionId && this.removeConnection(origin, connectionId); - if (err) { + // For context and todos related to the error message match, see https://github.com/MetaMask/metamask-extension/issues/26337 + if (err && !err.message?.match('Premature close')) { log.error(err); } }, ); - - // Used to show wallet liveliness to the provider - if (subjectType !== SubjectType.Internal) { - this._notifyChainChangeForConnection({ engine }, origin); - } } + ///: END:ONLY_INCLUDE_IF /** * For snaps running in workers. @@ -6302,9 +6470,23 @@ export default class MetamaskController extends EventEmitter { }), ); - engine.push(createUnsupportedMethodMiddleware()); + engine.push(createUnsupportedMethodMiddleware(UNSUPPORTED_RPC_METHODS)); - // Legacy RPC methods that need to be implemented _ahead of_ the permission + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + engine.push((req, res, next, end) => + caipPermissionAdapterMiddleware(req, res, next, end, { + getCaveat: this.permissionController.getCaveat.bind( + this.permissionController, + ), + getNetworkConfigurationByNetworkClientId: + this.networkController.getNetworkConfigurationByNetworkClientId.bind( + this.networkController, + ), + }), + ); + ///: END:ONLY_INCLUDE_IF + + // Legacy RPC method that needs to be implemented _ahead of_ the permission // middleware. engine.push( createEthAccountsMethodMiddleware({ @@ -6610,22 +6792,277 @@ export default class MetamaskController extends EventEmitter { } /** - * A method for creating a CAIP provider that is safely restricted for the requesting subject. + * A method for creating a provider that is safely restricted for the requesting subject. * * @param {object} options - Provider engine options * @param {string} options.origin - The origin of the sender * @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab */ + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) setupProviderEngineCaip({ origin, tabId }) { const engine = new JsonRpcEngine(); - engine.push((request, _res, _next, end) => { - console.log('CAIP request received', { origin, tabId, request }); - return end(new Error('CAIP RPC Pipeline not yet implemented.')); + // Append origin to each request + engine.push(createOriginMiddleware({ origin })); + + // Append tabId to each request if it exists + if (tabId) { + engine.push(createTabIdMiddleware({ tabId })); + } + + engine.push(createLoggerMiddleware({ origin })); + + engine.push((req, _res, next, end) => { + if ( + ![ + MESSAGE_TYPE.WALLET_CREATE_SESSION, + MESSAGE_TYPE.WALLET_INVOKE_METHOD, + MESSAGE_TYPE.WALLET_GET_SESSION, + MESSAGE_TYPE.WALLET_REVOKE_SESSION, + ].includes(req.method) + ) { + return end(new Error('Invalid method')); // TODO: Use a proper error + } + return next(); + }); + + // TODO: Uncomment this when wallet lifecycle methods are added to api-specs + engine.push(multichainMethodCallValidatorMiddleware); + const middlewareMaker = makeMethodMiddlewareMaker([ + walletRevokeSession, + walletGetSession, + walletInvokeMethod, + walletCreateSession, + ]); + + engine.push( + middlewareMaker({ + grantPermissions: this.permissionController.grantPermissions.bind( + this.permissionController, + ), + findNetworkClientIdByChainId: + this.networkController.findNetworkClientIdByChainId.bind( + this.networkController, + ), + listAccounts: this.accountsController.listAccounts.bind( + this.accountsController, + ), + addNetwork: this.networkController.addNetwork.bind( + this.networkController, + ), + removeNetwork: this.networkController.removeNetwork.bind( + this.networkController, + ), + requestPermissionApprovalForOrigin: + this.requestPermissionApprovalForOrigin.bind(this, origin), + sendMetrics: this.metaMetricsController.trackEvent.bind( + this.metaMetricsController, + ), + metamaskState: this.getState(), + getCaveat: this.permissionController.getCaveat.bind( + this.permissionController, + ), + getSelectedNetworkClientId: () => + this.networkController.state.selectedNetworkClientId, + revokePermission: this.permissionController.revokePermission.bind( + this.permissionController, + ), + }), + ); + + // TODO: Does this need to go before the wallet_createSession middleware? + // Add a middleware that will switch chain on each request (as needed) + const requestQueueMiddleware = createQueuedRequestMiddleware({ + enqueueRequest: this.queuedRequestController.enqueueRequest.bind( + this.queuedRequestController, + ), + useRequestQueue: this.preferencesController.getUseRequestQueue.bind( + this.preferencesController, + ), + shouldEnqueueRequest: (request) => { + return methodsRequiringNetworkSwitch.includes(request.method); + }, + }); + engine.push(requestQueueMiddleware); + + engine.push( + createUnsupportedMethodMiddleware([ + ...UNSUPPORTED_RPC_METHODS, + 'eth_requestAccounts', + 'eth_accounts', + ]), + ); + + engine.push( + createMultichainMethodMiddleware({ + subjectType: SubjectType.Website, // TODO: this should probably be passed in + + // Miscellaneous + addSubjectMetadata: + this.subjectMetadataController.addSubjectMetadata.bind( + this.subjectMetadataController, + ), + getProviderState: this.getProviderState.bind(this), + handleWatchAssetRequest: this.handleWatchAssetRequest.bind(this), + requestUserApproval: + this.approvalController.addAndShowApprovalRequest.bind( + this.approvalController, + ), + startApprovalFlow: this.approvalController.startFlow.bind( + this.approvalController, + ), + endApprovalFlow: this.approvalController.endFlow.bind( + this.approvalController, + ), + getCaveat: ({ target, caveatType }) => { + try { + return this.permissionController.getCaveat( + origin, + target, + caveatType, + ); + } catch (e) { + if (e instanceof PermissionDoesNotExistError) { + // suppress expected error in case that the origin + // does not have the target permission yet + } else { + throw e; + } + } + + return undefined; + }, + addNetwork: this.networkController.addNetwork.bind( + this.networkController, + ), + updateNetwork: this.networkController.updateNetwork.bind( + this.networkController, + ), + setActiveNetwork: async (networkClientId) => { + await this.networkController.setActiveNetwork(networkClientId); + // if the origin has the CAIP-25 permission + // we set per dapp network selection state + if ( + this.permissionController.hasPermission( + origin, + Caip25EndowmentPermissionName, + ) + ) { + this.selectedNetworkController.setNetworkClientIdForDomain( + origin, + networkClientId, + ); + } + }, + getNetworkConfigurationByChainId: + this.networkController.getNetworkConfigurationByChainId.bind( + this.networkController, + ), + // TODO refactor `add-ethereum-chain` handler so that this hook can be removed from multichain middleware + getCurrentChainIdForDomain: (domain) => { + const networkClientId = + this.selectedNetworkController.getNetworkClientIdForDomain(domain); + const { chainId } = + this.networkController.getNetworkConfigurationByNetworkClientId( + networkClientId, + ); + return chainId; + }, + + // Web3 shim-related + getWeb3ShimUsageState: this.alertController.getWeb3ShimUsageState.bind( + this.alertController, + ), + setWeb3ShimUsageRecorded: + this.alertController.setWeb3ShimUsageRecorded.bind( + this.alertController, + ), + + requestPermissionApprovalForOrigin: + this.requestPermissionApprovalForOrigin.bind(this, origin), + updateCaveat: this.permissionController.updateCaveat.bind( + this.permissionController, + ), + grantPermissions: this.permissionController.grantPermissions.bind( + this.permissionController, + ), + }), + ); + + engine.push(this.metamaskMiddleware); + + // TODO: Might be able to DRY this with the stateChange event + try { + const caip25Caveat = this.permissionController.getCaveat( + origin, + Caip25EndowmentPermissionName, + Caip25CaveatType, + ); + + // add new notification subscriptions for changed authorizations + const sessionScopes = getSessionScopes(caip25Caveat.value); + + // if the eth_subscription notification is in the scope and eth_subscribe is in the methods + // then get the subscriptionManager going for that scope + Object.entries(sessionScopes).forEach(([scope, scopeObject]) => { + if ( + scopeObject.notifications.includes('eth_subscription') && + scopeObject.methods.includes('eth_subscribe') + ) { + const subscriptionManager = + this.multichainSubscriptionManager.subscribe({ + scope, + origin, + tabId, + }); + this.multichainMiddlewareManager.addMiddleware({ + scope, + origin, + tabId, + middleware: subscriptionManager.middleware, + }); + } + }); + } catch (err) { + // noop + } + + this.multichainSubscriptionManager.on( + 'notification', + (targetOrigin, targetTabId, message) => { + if (origin === targetOrigin && tabId === targetTabId) { + engine.emit('notification', message); + } + }, + ); + + engine.push( + this.multichainMiddlewareManager.generateMultichainMiddlewareForOriginAndTabId( + origin, + tabId, + ), + ); + + engine.push((req, res, _next, end) => { + const { provider } = this.networkController.getNetworkClientById( + req.networkClientId, + ); + + // send request to provider + provider.sendAsync(req, (err, providerRes) => { + // forward any error + if (err instanceof Error) { + return end(err); + } + // copy provider response onto original response + Object.assign(res, providerRes); + return end(); + }); }); return engine; } + ///: END:ONLY_INCLUDE_IF /** * TODO:LegacyProvider: Delete @@ -6658,9 +7095,11 @@ export default class MetamaskController extends EventEmitter { * @param {string} origin - The connection's origin string. * @param {object} options - Data associated with the connection * @param {object} options.engine - The connection's JSON Rpc Engine + * @param {number} options.tabId - The tabId for the connection + * @param {API_TYPE} options.apiType - The API type for the connection * @returns {string} The connection's id (so that it can be deleted later) */ - addConnection(origin, { engine }) { + addConnection(origin, { tabId, apiType, engine }) { if (origin === ORIGIN_METAMASK) { return null; } @@ -6671,6 +7110,8 @@ export default class MetamaskController extends EventEmitter { const id = nanoid(); this.connections[origin][id] = { + tabId, + apiType, engine, }; @@ -6726,12 +7167,16 @@ export default class MetamaskController extends EventEmitter { * * @param {string} origin - The connection's origin string. * @param {unknown} payload - The event payload. + * @param apiType */ - notifyConnections(origin, payload) { + notifyConnections(origin, payload, apiType) { const connections = this.connections[origin]; if (connections) { Object.values(connections).forEach((conn) => { + if (apiType && conn.apiType !== apiType) { + return; + } if (conn.engine) { conn.engine.emit('notification', payload); } @@ -6751,8 +7196,9 @@ export default class MetamaskController extends EventEmitter { * are sent. * * @param {unknown} payload - The event payload, or payload getter function. + * @param apiType */ - notifyAllConnections(payload) { + notifyAllConnections(payload, apiType) { const getPayload = typeof payload === 'function' ? (origin) => payload(origin) @@ -6760,6 +7206,9 @@ export default class MetamaskController extends EventEmitter { Object.keys(this.connections).forEach((origin) => { Object.values(this.connections[origin]).forEach(async (conn) => { + if (apiType && conn.apiType !== apiType) { + return; + } try { this.notifyConnection(conn, await getPayload(origin)); } catch (err) { @@ -6829,7 +7278,7 @@ export default class MetamaskController extends EventEmitter { accounts: this.getPermittedAccounts(origin), }, }; - }); + }, API_TYPE.EIP1193); this.unMarkPasswordForgotten(); @@ -6844,12 +7293,15 @@ export default class MetamaskController extends EventEmitter { * Notifies all connections that the extension is locked. */ _onLock() { - this.notifyAllConnections({ - method: NOTIFICATION_NAMES.unlockStateChanged, - params: { - isUnlocked: false, + this.notifyAllConnections( + { + method: NOTIFICATION_NAMES.unlockStateChanged, + params: { + isUnlocked: false, + }, }, - }); + API_TYPE.EIP1193, + ); // In the current implementation, this handler is triggered by a // KeyringController event. Other controllers subscribe to the 'lock' @@ -7416,35 +7868,60 @@ export default class MetamaskController extends EventEmitter { _notifyAccountsChange(origin, newAccounts) { if (this.isUnlocked()) { - this.notifyConnections(origin, { - method: NOTIFICATION_NAMES.accountsChanged, - // This should be the same as the return value of `eth_accounts`, - // namely an array of the current / most recently selected Ethereum - // account. - params: - newAccounts.length < 2 - ? // If the length is 1 or 0, the accounts are sorted by definition. - newAccounts - : // If the length is 2 or greater, we have to execute - // `eth_accounts` vi this method. - this.getPermittedAccounts(origin), - }); + this.notifyConnections( + origin, + { + method: NOTIFICATION_NAMES.accountsChanged, + // This should be the same as the return value of `eth_accounts`, + // namely an array of the current / most recently selected Ethereum + // account. + params: + newAccounts.length < 2 + ? // If the length is 1 or 0, the accounts are sorted by definition. + newAccounts + : // If the length is 2 or greater, we have to execute + // `eth_accounts` vi this method. + this.getPermittedAccounts(origin), + }, + API_TYPE.EIP1193, + ); } this.permissionLogController.updateAccountsHistory(origin, newAccounts); } + async _notifyAuthorizationChange(origin, newAuthorization) { + if (this.isUnlocked()) { + this.notifyConnections( + origin, + { + method: NOTIFICATION_NAMES.sessionChanged, + params: { + sessionScopes: getSessionScopes(newAuthorization), + }, + }, + API_TYPE.CAIP_MULTICHAIN, + ); + } + } + async _notifyChainChange() { if (this.preferencesController.getUseRequestQueue()) { - this.notifyAllConnections(async (origin) => ({ - method: NOTIFICATION_NAMES.chainChanged, - params: await this.getProviderNetworkState(origin), - })); + this.notifyAllConnections( + async (origin) => ({ + method: NOTIFICATION_NAMES.chainChanged, + params: await this.getProviderNetworkState(origin), + }), + API_TYPE.EIP1193, + ); } else { - this.notifyAllConnections({ - method: NOTIFICATION_NAMES.chainChanged, - params: await this.getProviderNetworkState(), - }); + this.notifyAllConnections( + { + method: NOTIFICATION_NAMES.chainChanged, + params: await this.getProviderNetworkState(), + }, + API_TYPE.EIP1193, + ); } } diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 5255622956c3..a13c62f4ced6 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -119,7 +119,8 @@ const createLoggerMiddlewareMock = () => (req, res, next) => { }; jest.mock('./lib/createLoggerMiddleware', () => createLoggerMiddlewareMock); -const rpcMethodMiddlewareMock = { +jest.mock('./lib/rpc-method-middleware', () => ({ + ...jest.requireActual('./lib/rpc-method-middleware'), createEip1193MethodMiddleware: () => (_req, _res, next, _end) => { next(); }, @@ -132,8 +133,7 @@ const rpcMethodMiddlewareMock = { createUnsupportedMethodMiddleware: () => (_req, _res, next, _end) => { next(); }, -}; -jest.mock('./lib/rpc-method-middleware', () => rpcMethodMiddlewareMock); +})); const KNOWN_PUBLIC_KEY = '02065bc80d3d12b3688e4ad5ab1e9eda6adf24aec2518bfc21b87c99d4c5077ab0'; @@ -2686,8 +2686,6 @@ describe('MetaMaskController', () => { }); describe('#setupUntrustedCommunicationEip1193', () => { - const mockTxParams = { from: TEST_ADDRESS }; - beforeEach(() => { initializeMockMiddlewareLog(); metamaskController.preferencesController.setSecurityAlertsEnabled( @@ -2771,6 +2769,7 @@ describe('MetaMaskController', () => { expect.anything(), 'test.metamask-phishing.io', ); + streamTest.end(); }); it('adds a tabId, origin and networkClient to requests', async () => { @@ -2794,8 +2793,7 @@ describe('MetaMaskController', () => { const message = { id: 1999133338649204, jsonrpc: '2.0', - params: [{ ...mockTxParams }], - method: 'eth_sendTransaction', + method: 'eth_chainId', }; await new Promise((resolve) => { streamTest.write( @@ -2823,6 +2821,7 @@ describe('MetaMaskController', () => { }, ); }); + streamTest.end(); }); it('should add only origin to request if tabId not provided', async () => { @@ -2843,10 +2842,8 @@ describe('MetaMaskController', () => { }); const message = { - id: 1999133338649204, jsonrpc: '2.0', - params: [{ ...mockTxParams }], - method: 'eth_sendTransaction', + method: 'eth_chainId', }; await new Promise((resolve) => { streamTest.write( @@ -2869,15 +2866,247 @@ describe('MetaMaskController', () => { }, ); }); + streamTest.end(); }); - it.todo( - 'should only process `metamask-provider` multiplex formatted messages', - ); + it('should only process `metamask-provider` multiplex formatted messages', async () => { + const messageSender = { + url: 'http://mycrypto.com', + tab: { id: 456 }, + }; + const streamTest = createThroughStream((chunk, _, cb) => { + if (chunk.data && chunk.data.method) { + cb(null, chunk); + return; + } + cb(); + }); + + metamaskController.setupUntrustedCommunicationEip1193({ + connectionStream: streamTest, + sender: messageSender, + }); + + const message = { + jsonrpc: '2.0', + method: 'eth_chainId', + }; + await new Promise((resolve) => { + streamTest.write( + { + type: 'caip-x', + data: { + method: 'wallet_invokeMethod', + params: { + scope: 'eip155:1', + request: message, + }, + }, + }, + null, + () => { + setTimeout(() => { + expect(loggerMiddlewareMock.requests).toHaveLength(0); + resolve(); + }); + }, + ); + }); + await new Promise((resolve) => { + streamTest.write( + { + name: 'metamask-provider', + data: message, + }, + null, + () => { + setTimeout(() => { + expect(loggerMiddlewareMock.requests).toHaveLength(1); + resolve(); + }); + }, + ); + }); + streamTest.end(); + }); }); describe('#setupUntrustedCommunicationCaip', () => { - it.todo('should only process `caip-x` CAIP formatted messages'); + beforeEach(() => { + initializeMockMiddlewareLog(); + jest + .spyOn(metamaskController.onboardingController, 'state', 'get') + .mockReturnValue({ completedOnboarding: true }); + }); + + afterAll(() => { + tearDownMockMiddlewareLog(); + }); + + it('adds a tabId and origin to requests', async () => { + const messageSender = { + url: 'http://mycrypto.com', + tab: { id: 456 }, + }; + const streamTest = createThroughStream((chunk, _, cb) => { + if (chunk.data && chunk.data.method) { + cb(null, chunk); + return; + } + cb(); + }); + + metamaskController.setupUntrustedCommunicationCaip({ + connectionStream: streamTest, + sender: messageSender, + }); + + const message = { + jsonrpc: '2.0', + method: 'eth_chainId', + }; + await new Promise((resolve) => { + streamTest.write( + { + type: 'caip-x', + data: { + method: 'wallet_invokeMethod', + params: { + scope: 'eip155:1', + request: message, + }, + }, + }, + null, + () => { + setTimeout(() => { + expect(loggerMiddlewareMock.requests[0]).toHaveProperty( + 'origin', + 'http://mycrypto.com', + ); + expect(loggerMiddlewareMock.requests[0]).toHaveProperty( + 'tabId', + 456, + ); + resolve(); + }); + }, + ); + }); + streamTest.end(); + }); + + it('should add only origin to request if tabId not provided', async () => { + const messageSender = { + url: 'http://mycrypto.com', + }; + const streamTest = createThroughStream((chunk, _, cb) => { + if (chunk.data && chunk.data.method) { + cb(null, chunk); + return; + } + cb(); + }); + + metamaskController.setupUntrustedCommunicationCaip({ + connectionStream: streamTest, + sender: messageSender, + }); + + const message = { + jsonrpc: '2.0', + method: 'eth_chainId', + }; + await new Promise((resolve) => { + streamTest.write( + { + type: 'caip-x', + data: { + method: 'wallet_invokeMethod', + params: { + scope: 'eip155:1', + request: message, + }, + }, + }, + null, + () => { + setTimeout(() => { + expect(loggerMiddlewareMock.requests[0]).not.toHaveProperty( + 'tabId', + ); + expect(loggerMiddlewareMock.requests[0]).toHaveProperty( + 'origin', + 'http://mycrypto.com', + ); + resolve(); + }); + }, + ); + }); + streamTest.end(); + }); + + it('should only process `caip-x` CAIP formatted messages', async () => { + const messageSender = { + url: 'http://mycrypto.com', + tab: { id: 456 }, + }; + const streamTest = createThroughStream((chunk, _, cb) => { + if (chunk.data && chunk.data.method) { + cb(null, chunk); + return; + } + cb(); + }); + + metamaskController.setupUntrustedCommunicationCaip({ + connectionStream: streamTest, + sender: messageSender, + }); + + const message = { + jsonrpc: '2.0', + method: 'eth_chainId', + }; + await new Promise((resolve) => { + streamTest.write( + { + name: 'metamask-provider', + data: message, + }, + null, + () => { + setTimeout(() => { + expect(loggerMiddlewareMock.requests).toHaveLength(0); + resolve(); + }); + }, + ); + }); + await new Promise((resolve) => { + streamTest.write( + { + type: 'caip-x', + data: { + method: 'wallet_invokeMethod', + params: { + scope: 'eip155:1', + request: message, + }, + }, + }, + null, + () => { + setTimeout(() => { + expect(loggerMiddlewareMock.requests).toHaveLength(1); + resolve(); + }); + }, + ); + }); + streamTest.end(); + }); }); describe('#setupTrustedCommunication', () => { diff --git a/builds.yml b/builds.yml index 43cf02d3c8e5..4627724d032e 100644 --- a/builds.yml +++ b/builds.yml @@ -277,8 +277,6 @@ env: - NODE_DEBUG: '' # Used by react-devtools-core - EDITOR_URL: '' - # Determines if Barad Dur features should be used - - BARAD_DUR: '' # Determines if feature flagged Chain permissions - CHAIN_PERMISSIONS: '' # Determines if Portfolio View UI should be shown diff --git a/development/build/manifest.js b/development/build/manifest.js index bc5325b372eb..551907062557 100644 --- a/development/build/manifest.js +++ b/development/build/manifest.js @@ -7,9 +7,6 @@ const { isManifestV3 } = require('../../shared/modules/mv3.utils'); const baseManifest = isManifestV3 ? require('../../app/manifest/v3/_base.json') : require('../../app/manifest/v2/_base.json'); -const baradDurManifest = isManifestV3 - ? require('../../app/manifest/v3/_barad_dur.json') - : require('../../app/manifest/v2/_barad_dur.json'); const { loadBuildTypesConfig } = require('../lib/build-type'); const { TASKS, ENVIRONMENT } = require('./constants'); @@ -42,7 +39,6 @@ function createManifestTasks({ ); const result = mergeWith( cloneDeep(baseManifest), - process.env.BARAD_DUR ? cloneDeep(baradDurManifest) : {}, platformModifications, browserVersionMap[platform], await getBuildModifications(buildType, platform), diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 88f0c695b713..add0f54e5354 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -54,17 +54,13 @@ }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "browserify>buffer": true, - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, "webpack>events": true } }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "browserify>buffer": true, - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, "webpack>events": true } }, @@ -73,7 +69,12 @@ "TextEncoder": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "eth-lattice-keyring>@ethereumjs/tx>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": { "globals": { "TextEncoder": true } @@ -96,7 +97,7 @@ "@metamask/smart-transactions-controller>@ethereumjs/tx": { "packages": { "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true } @@ -105,7 +106,7 @@ "packages": { "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/rlp": true, + "eth-lattice-keyring>@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, "@ethersproject/providers": true, "browserify>buffer": true, @@ -115,14 +116,10 @@ }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { "packages": { - "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethersproject/providers": true, - "browserify>buffer": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true, - "browserify>insert-module-globals>is-buffer": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, + "@ethereumjs/tx>ethereum-cryptography": true } }, "@ethereumjs/tx>@ethereumjs/util": { @@ -130,7 +127,7 @@ "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": true, "browserify>buffer": true, "@ethereumjs/tx>ethereum-cryptography": true, "webpack>events": true, @@ -138,13 +135,24 @@ "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true } }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": { + "globals": { + "console.warn": true, + "fetch": true + }, + "packages": { + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true + } + }, "@metamask/smart-transactions-controller>@ethereumjs/util": { "globals": { "console.warn": true, "fetch": true }, "packages": { - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "@ethereumjs/tx>ethereum-cryptography": true, "webpack>events": true } @@ -605,6 +613,19 @@ "process": true } }, + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": true, + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer>@json-schema-tools/traverse": true, + "@metamask/rpc-errors>fast-safe-stringify": true + } + }, + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver>@json-schema-spec/json-pointer": true, + "@open-rpc/test-coverage>isomorphic-fetch": true + } + }, "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { "packages": { "@ethereumjs/tx": true, @@ -612,7 +633,7 @@ "@keystonehq/bc-ur-registry-eth": true, "browserify>buffer": true, "@metamask/eth-trezor-keyring>hdkey": true, - "eth-lattice-keyring>rlp": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, "uuid": true } }, @@ -645,7 +666,7 @@ "@metamask/obs-store": true, "browserify>buffer": true, "webpack>events": true, - "@keystonehq/metamask-airgapped-keyring>rlp": true, + "ethereumjs-util>rlp": true, "uuid": true } }, @@ -1407,12 +1428,19 @@ } }, "@metamask/multichain": { + "globals": { + "console.error": true + }, "packages": { - "@metamask/multichain>@metamask/api-specs": true, + "@metamask/api-specs": true, "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-filters": true, "@metamask/permission-controller": true, "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, "@metamask/utils": true, + "@open-rpc/schema-utils-js": true, + "@metamask/multichain>jsonschema": true, "lodash": true } }, @@ -1686,7 +1714,7 @@ "@metamask/utils": true, "browserify>buffer": true, "webpack>events": true, - "@metamask/message-manager>jsonschema": true, + "@metamask/multichain>jsonschema": true, "uuid": true } }, @@ -2344,10 +2372,15 @@ "crypto": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "@open-rpc/schema-utils-js": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer": true, + "@open-rpc/schema-utils-js>@json-schema-tools/meta-schema": true, + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": true, + "@open-rpc/meta-schema": true, + "eslint>ajv": true, + "@metamask/rpc-errors>fast-safe-stringify": true, + "@open-rpc/schema-utils-js>is-url": true } }, "@popperjs/core": { @@ -2869,6 +2902,17 @@ "define": true } }, + "eslint>ajv": { + "globals": { + "console": true + }, + "packages": { + "eslint>fast-deep-equal": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "eslint>ajv>json-schema-traverse": true, + "uri-js": true + } + }, "chalk>ansi-styles": { "packages": { "chalk>ansi-styles>color-convert": true @@ -3053,11 +3097,6 @@ "define": true } }, - "eth-lattice-keyring>gridplus-sdk>bitwise": { - "packages": { - "browserify>buffer": true - } - }, "blo": { "globals": { "btoa": true @@ -3156,6 +3195,11 @@ "@ensdomains/content-hash>multihashes>multibase>base-x": true } }, + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": { + "packages": { + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58>base-x": true + } + }, "ethereumjs-util>ethereum-cryptography>bs58check": { "packages": { "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, @@ -3163,6 +3207,12 @@ "koa>content-disposition>safe-buffer": true } }, + "eth-lattice-keyring>gridplus-sdk>bs58check": { + "packages": { + "@noble/hashes": true, + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": true + } + }, "buffer": { "globals": { "console": true @@ -3544,6 +3594,17 @@ "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, + "eth-lattice-keyring>gridplus-sdk>secp256k1>elliptic": { + "packages": { + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true + } + }, "string.prototype.matchall>call-bind>es-define-property": { "packages": { "string.prototype.matchall>get-intrinsic": true @@ -3563,16 +3624,6 @@ "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true } }, - "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": { - "globals": { - "intToBuffer": true - }, - "packages": { - "bn.js": true, - "buffer": true, - "@metamask/ethjs>js-sha3": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" @@ -3629,15 +3680,6 @@ "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { - "globals": { - "TextDecoder": true, - "crypto": true - }, - "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true - } - }, "ethereumjs-util>ethereum-cryptography": { "packages": { "browserify>buffer": true, @@ -3678,7 +3720,7 @@ "ethereumjs-util>create-hash": true, "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, "browserify>insert-module-globals>is-buffer": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true + "ethereumjs-util>rlp": true } }, "@metamask/keyring-controller>ethereumjs-wallet": { @@ -3874,24 +3916,23 @@ }, "packages": { "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, "@ethersproject/abi": true, + "@metamask/eth-sig-util": true, "eth-lattice-keyring>gridplus-sdk>aes-js": true, "@metamask/keyring-api>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>bitwise": true, "bn.js": true, "eth-lattice-keyring>gridplus-sdk>borc": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, + "eth-lattice-keyring>gridplus-sdk>bs58check": true, "browserify>buffer": true, "@ethereumjs/tx>@ethereumjs/common>crc-32": true, "eth-lattice-keyring>gridplus-sdk>elliptic": true, - "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, "ethers>@ethersproject/sha2>hash.js": true, "@metamask/ethjs>js-sha3": true, "lodash": true, - "eth-lattice-keyring>rlp": true, - "ganache>secp256k1": true, + "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true } }, @@ -4093,7 +4134,16 @@ "globals": { "URL": true, "URLSearchParams": true, - "location": true + "location": true, + "navigator": true + } + }, + "@open-rpc/test-coverage>isomorphic-fetch": { + "globals": { + "fetch.bind": true + }, + "packages": { + "@open-rpc/test-coverage>isomorphic-fetch>whatwg-fetch": true } }, "@ensdomains/content-hash>js-base64": { @@ -4122,7 +4172,7 @@ "define": true } }, - "@metamask/message-manager>jsonschema": { + "@metamask/multichain>jsonschema": { "packages": { "browserify>url": true } @@ -5114,10 +5164,9 @@ "pumpify>inherits": true } }, - "@keystonehq/metamask-airgapped-keyring>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { + "globals": { + "TextEncoder": true } }, "eth-lattice-keyring>rlp": { @@ -5131,12 +5180,6 @@ "browserify>buffer": true } }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, "wait-on>rxjs": { "globals": { "cancelAnimationFrame": true, @@ -5179,6 +5222,11 @@ "@metamask/ppom-validator>elliptic": true } }, + "eth-lattice-keyring>gridplus-sdk>secp256k1": { + "packages": { + "eth-lattice-keyring>gridplus-sdk>secp256k1>elliptic": true + } + }, "semver": { "globals": { "console.error": true @@ -5554,6 +5602,19 @@ "define": true } }, + "@open-rpc/test-coverage>isomorphic-fetch>whatwg-fetch": { + "globals": { + "AbortController": true, + "Blob": true, + "FileReader": true, + "FormData": true, + "URLSearchParams.prototype.isPrototypeOf": true, + "XMLHttpRequest": true, + "console.warn": true, + "define": true, + "setTimeout": true + } + }, "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { "packages": { "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 88f0c695b713..add0f54e5354 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -54,17 +54,13 @@ }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "browserify>buffer": true, - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, "webpack>events": true } }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "browserify>buffer": true, - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, "webpack>events": true } }, @@ -73,7 +69,12 @@ "TextEncoder": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "eth-lattice-keyring>@ethereumjs/tx>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": { "globals": { "TextEncoder": true } @@ -96,7 +97,7 @@ "@metamask/smart-transactions-controller>@ethereumjs/tx": { "packages": { "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true } @@ -105,7 +106,7 @@ "packages": { "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/rlp": true, + "eth-lattice-keyring>@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, "@ethersproject/providers": true, "browserify>buffer": true, @@ -115,14 +116,10 @@ }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { "packages": { - "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethersproject/providers": true, - "browserify>buffer": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true, - "browserify>insert-module-globals>is-buffer": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, + "@ethereumjs/tx>ethereum-cryptography": true } }, "@ethereumjs/tx>@ethereumjs/util": { @@ -130,7 +127,7 @@ "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": true, "browserify>buffer": true, "@ethereumjs/tx>ethereum-cryptography": true, "webpack>events": true, @@ -138,13 +135,24 @@ "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true } }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": { + "globals": { + "console.warn": true, + "fetch": true + }, + "packages": { + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true + } + }, "@metamask/smart-transactions-controller>@ethereumjs/util": { "globals": { "console.warn": true, "fetch": true }, "packages": { - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "@ethereumjs/tx>ethereum-cryptography": true, "webpack>events": true } @@ -605,6 +613,19 @@ "process": true } }, + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": true, + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer>@json-schema-tools/traverse": true, + "@metamask/rpc-errors>fast-safe-stringify": true + } + }, + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver>@json-schema-spec/json-pointer": true, + "@open-rpc/test-coverage>isomorphic-fetch": true + } + }, "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { "packages": { "@ethereumjs/tx": true, @@ -612,7 +633,7 @@ "@keystonehq/bc-ur-registry-eth": true, "browserify>buffer": true, "@metamask/eth-trezor-keyring>hdkey": true, - "eth-lattice-keyring>rlp": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, "uuid": true } }, @@ -645,7 +666,7 @@ "@metamask/obs-store": true, "browserify>buffer": true, "webpack>events": true, - "@keystonehq/metamask-airgapped-keyring>rlp": true, + "ethereumjs-util>rlp": true, "uuid": true } }, @@ -1407,12 +1428,19 @@ } }, "@metamask/multichain": { + "globals": { + "console.error": true + }, "packages": { - "@metamask/multichain>@metamask/api-specs": true, + "@metamask/api-specs": true, "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-filters": true, "@metamask/permission-controller": true, "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, "@metamask/utils": true, + "@open-rpc/schema-utils-js": true, + "@metamask/multichain>jsonschema": true, "lodash": true } }, @@ -1686,7 +1714,7 @@ "@metamask/utils": true, "browserify>buffer": true, "webpack>events": true, - "@metamask/message-manager>jsonschema": true, + "@metamask/multichain>jsonschema": true, "uuid": true } }, @@ -2344,10 +2372,15 @@ "crypto": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "@open-rpc/schema-utils-js": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer": true, + "@open-rpc/schema-utils-js>@json-schema-tools/meta-schema": true, + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": true, + "@open-rpc/meta-schema": true, + "eslint>ajv": true, + "@metamask/rpc-errors>fast-safe-stringify": true, + "@open-rpc/schema-utils-js>is-url": true } }, "@popperjs/core": { @@ -2869,6 +2902,17 @@ "define": true } }, + "eslint>ajv": { + "globals": { + "console": true + }, + "packages": { + "eslint>fast-deep-equal": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "eslint>ajv>json-schema-traverse": true, + "uri-js": true + } + }, "chalk>ansi-styles": { "packages": { "chalk>ansi-styles>color-convert": true @@ -3053,11 +3097,6 @@ "define": true } }, - "eth-lattice-keyring>gridplus-sdk>bitwise": { - "packages": { - "browserify>buffer": true - } - }, "blo": { "globals": { "btoa": true @@ -3156,6 +3195,11 @@ "@ensdomains/content-hash>multihashes>multibase>base-x": true } }, + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": { + "packages": { + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58>base-x": true + } + }, "ethereumjs-util>ethereum-cryptography>bs58check": { "packages": { "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, @@ -3163,6 +3207,12 @@ "koa>content-disposition>safe-buffer": true } }, + "eth-lattice-keyring>gridplus-sdk>bs58check": { + "packages": { + "@noble/hashes": true, + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": true + } + }, "buffer": { "globals": { "console": true @@ -3544,6 +3594,17 @@ "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, + "eth-lattice-keyring>gridplus-sdk>secp256k1>elliptic": { + "packages": { + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true + } + }, "string.prototype.matchall>call-bind>es-define-property": { "packages": { "string.prototype.matchall>get-intrinsic": true @@ -3563,16 +3624,6 @@ "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true } }, - "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": { - "globals": { - "intToBuffer": true - }, - "packages": { - "bn.js": true, - "buffer": true, - "@metamask/ethjs>js-sha3": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" @@ -3629,15 +3680,6 @@ "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { - "globals": { - "TextDecoder": true, - "crypto": true - }, - "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true - } - }, "ethereumjs-util>ethereum-cryptography": { "packages": { "browserify>buffer": true, @@ -3678,7 +3720,7 @@ "ethereumjs-util>create-hash": true, "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, "browserify>insert-module-globals>is-buffer": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true + "ethereumjs-util>rlp": true } }, "@metamask/keyring-controller>ethereumjs-wallet": { @@ -3874,24 +3916,23 @@ }, "packages": { "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, "@ethersproject/abi": true, + "@metamask/eth-sig-util": true, "eth-lattice-keyring>gridplus-sdk>aes-js": true, "@metamask/keyring-api>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>bitwise": true, "bn.js": true, "eth-lattice-keyring>gridplus-sdk>borc": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, + "eth-lattice-keyring>gridplus-sdk>bs58check": true, "browserify>buffer": true, "@ethereumjs/tx>@ethereumjs/common>crc-32": true, "eth-lattice-keyring>gridplus-sdk>elliptic": true, - "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, "ethers>@ethersproject/sha2>hash.js": true, "@metamask/ethjs>js-sha3": true, "lodash": true, - "eth-lattice-keyring>rlp": true, - "ganache>secp256k1": true, + "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true } }, @@ -4093,7 +4134,16 @@ "globals": { "URL": true, "URLSearchParams": true, - "location": true + "location": true, + "navigator": true + } + }, + "@open-rpc/test-coverage>isomorphic-fetch": { + "globals": { + "fetch.bind": true + }, + "packages": { + "@open-rpc/test-coverage>isomorphic-fetch>whatwg-fetch": true } }, "@ensdomains/content-hash>js-base64": { @@ -4122,7 +4172,7 @@ "define": true } }, - "@metamask/message-manager>jsonschema": { + "@metamask/multichain>jsonschema": { "packages": { "browserify>url": true } @@ -5114,10 +5164,9 @@ "pumpify>inherits": true } }, - "@keystonehq/metamask-airgapped-keyring>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { + "globals": { + "TextEncoder": true } }, "eth-lattice-keyring>rlp": { @@ -5131,12 +5180,6 @@ "browserify>buffer": true } }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, "wait-on>rxjs": { "globals": { "cancelAnimationFrame": true, @@ -5179,6 +5222,11 @@ "@metamask/ppom-validator>elliptic": true } }, + "eth-lattice-keyring>gridplus-sdk>secp256k1": { + "packages": { + "eth-lattice-keyring>gridplus-sdk>secp256k1>elliptic": true + } + }, "semver": { "globals": { "console.error": true @@ -5554,6 +5602,19 @@ "define": true } }, + "@open-rpc/test-coverage>isomorphic-fetch>whatwg-fetch": { + "globals": { + "AbortController": true, + "Blob": true, + "FileReader": true, + "FormData": true, + "URLSearchParams.prototype.isPrototypeOf": true, + "XMLHttpRequest": true, + "console.warn": true, + "define": true, + "setTimeout": true + } + }, "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { "packages": { "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 88f0c695b713..add0f54e5354 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -54,17 +54,13 @@ }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "browserify>buffer": true, - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, "webpack>events": true } }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "browserify>buffer": true, - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, "webpack>events": true } }, @@ -73,7 +69,12 @@ "TextEncoder": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "eth-lattice-keyring>@ethereumjs/tx>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": { "globals": { "TextEncoder": true } @@ -96,7 +97,7 @@ "@metamask/smart-transactions-controller>@ethereumjs/tx": { "packages": { "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true } @@ -105,7 +106,7 @@ "packages": { "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/rlp": true, + "eth-lattice-keyring>@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, "@ethersproject/providers": true, "browserify>buffer": true, @@ -115,14 +116,10 @@ }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { "packages": { - "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethersproject/providers": true, - "browserify>buffer": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true, - "browserify>insert-module-globals>is-buffer": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, + "@ethereumjs/tx>ethereum-cryptography": true } }, "@ethereumjs/tx>@ethereumjs/util": { @@ -130,7 +127,7 @@ "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": true, "browserify>buffer": true, "@ethereumjs/tx>ethereum-cryptography": true, "webpack>events": true, @@ -138,13 +135,24 @@ "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true } }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": { + "globals": { + "console.warn": true, + "fetch": true + }, + "packages": { + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true + } + }, "@metamask/smart-transactions-controller>@ethereumjs/util": { "globals": { "console.warn": true, "fetch": true }, "packages": { - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "@ethereumjs/tx>ethereum-cryptography": true, "webpack>events": true } @@ -605,6 +613,19 @@ "process": true } }, + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": true, + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer>@json-schema-tools/traverse": true, + "@metamask/rpc-errors>fast-safe-stringify": true + } + }, + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver>@json-schema-spec/json-pointer": true, + "@open-rpc/test-coverage>isomorphic-fetch": true + } + }, "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { "packages": { "@ethereumjs/tx": true, @@ -612,7 +633,7 @@ "@keystonehq/bc-ur-registry-eth": true, "browserify>buffer": true, "@metamask/eth-trezor-keyring>hdkey": true, - "eth-lattice-keyring>rlp": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, "uuid": true } }, @@ -645,7 +666,7 @@ "@metamask/obs-store": true, "browserify>buffer": true, "webpack>events": true, - "@keystonehq/metamask-airgapped-keyring>rlp": true, + "ethereumjs-util>rlp": true, "uuid": true } }, @@ -1407,12 +1428,19 @@ } }, "@metamask/multichain": { + "globals": { + "console.error": true + }, "packages": { - "@metamask/multichain>@metamask/api-specs": true, + "@metamask/api-specs": true, "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-filters": true, "@metamask/permission-controller": true, "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, "@metamask/utils": true, + "@open-rpc/schema-utils-js": true, + "@metamask/multichain>jsonschema": true, "lodash": true } }, @@ -1686,7 +1714,7 @@ "@metamask/utils": true, "browserify>buffer": true, "webpack>events": true, - "@metamask/message-manager>jsonschema": true, + "@metamask/multichain>jsonschema": true, "uuid": true } }, @@ -2344,10 +2372,15 @@ "crypto": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "@open-rpc/schema-utils-js": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer": true, + "@open-rpc/schema-utils-js>@json-schema-tools/meta-schema": true, + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": true, + "@open-rpc/meta-schema": true, + "eslint>ajv": true, + "@metamask/rpc-errors>fast-safe-stringify": true, + "@open-rpc/schema-utils-js>is-url": true } }, "@popperjs/core": { @@ -2869,6 +2902,17 @@ "define": true } }, + "eslint>ajv": { + "globals": { + "console": true + }, + "packages": { + "eslint>fast-deep-equal": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "eslint>ajv>json-schema-traverse": true, + "uri-js": true + } + }, "chalk>ansi-styles": { "packages": { "chalk>ansi-styles>color-convert": true @@ -3053,11 +3097,6 @@ "define": true } }, - "eth-lattice-keyring>gridplus-sdk>bitwise": { - "packages": { - "browserify>buffer": true - } - }, "blo": { "globals": { "btoa": true @@ -3156,6 +3195,11 @@ "@ensdomains/content-hash>multihashes>multibase>base-x": true } }, + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": { + "packages": { + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58>base-x": true + } + }, "ethereumjs-util>ethereum-cryptography>bs58check": { "packages": { "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, @@ -3163,6 +3207,12 @@ "koa>content-disposition>safe-buffer": true } }, + "eth-lattice-keyring>gridplus-sdk>bs58check": { + "packages": { + "@noble/hashes": true, + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": true + } + }, "buffer": { "globals": { "console": true @@ -3544,6 +3594,17 @@ "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, + "eth-lattice-keyring>gridplus-sdk>secp256k1>elliptic": { + "packages": { + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true + } + }, "string.prototype.matchall>call-bind>es-define-property": { "packages": { "string.prototype.matchall>get-intrinsic": true @@ -3563,16 +3624,6 @@ "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true } }, - "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": { - "globals": { - "intToBuffer": true - }, - "packages": { - "bn.js": true, - "buffer": true, - "@metamask/ethjs>js-sha3": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" @@ -3629,15 +3680,6 @@ "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { - "globals": { - "TextDecoder": true, - "crypto": true - }, - "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true - } - }, "ethereumjs-util>ethereum-cryptography": { "packages": { "browserify>buffer": true, @@ -3678,7 +3720,7 @@ "ethereumjs-util>create-hash": true, "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, "browserify>insert-module-globals>is-buffer": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true + "ethereumjs-util>rlp": true } }, "@metamask/keyring-controller>ethereumjs-wallet": { @@ -3874,24 +3916,23 @@ }, "packages": { "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, "@ethersproject/abi": true, + "@metamask/eth-sig-util": true, "eth-lattice-keyring>gridplus-sdk>aes-js": true, "@metamask/keyring-api>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>bitwise": true, "bn.js": true, "eth-lattice-keyring>gridplus-sdk>borc": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, + "eth-lattice-keyring>gridplus-sdk>bs58check": true, "browserify>buffer": true, "@ethereumjs/tx>@ethereumjs/common>crc-32": true, "eth-lattice-keyring>gridplus-sdk>elliptic": true, - "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, "ethers>@ethersproject/sha2>hash.js": true, "@metamask/ethjs>js-sha3": true, "lodash": true, - "eth-lattice-keyring>rlp": true, - "ganache>secp256k1": true, + "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true } }, @@ -4093,7 +4134,16 @@ "globals": { "URL": true, "URLSearchParams": true, - "location": true + "location": true, + "navigator": true + } + }, + "@open-rpc/test-coverage>isomorphic-fetch": { + "globals": { + "fetch.bind": true + }, + "packages": { + "@open-rpc/test-coverage>isomorphic-fetch>whatwg-fetch": true } }, "@ensdomains/content-hash>js-base64": { @@ -4122,7 +4172,7 @@ "define": true } }, - "@metamask/message-manager>jsonschema": { + "@metamask/multichain>jsonschema": { "packages": { "browserify>url": true } @@ -5114,10 +5164,9 @@ "pumpify>inherits": true } }, - "@keystonehq/metamask-airgapped-keyring>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { + "globals": { + "TextEncoder": true } }, "eth-lattice-keyring>rlp": { @@ -5131,12 +5180,6 @@ "browserify>buffer": true } }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, "wait-on>rxjs": { "globals": { "cancelAnimationFrame": true, @@ -5179,6 +5222,11 @@ "@metamask/ppom-validator>elliptic": true } }, + "eth-lattice-keyring>gridplus-sdk>secp256k1": { + "packages": { + "eth-lattice-keyring>gridplus-sdk>secp256k1>elliptic": true + } + }, "semver": { "globals": { "console.error": true @@ -5554,6 +5602,19 @@ "define": true } }, + "@open-rpc/test-coverage>isomorphic-fetch>whatwg-fetch": { + "globals": { + "AbortController": true, + "Blob": true, + "FileReader": true, + "FormData": true, + "URLSearchParams.prototype.isPrototypeOf": true, + "XMLHttpRequest": true, + "console.warn": true, + "define": true, + "setTimeout": true + } + }, "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { "packages": { "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index a71f3106f748..bc6db42706ff 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -54,17 +54,13 @@ }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "browserify>buffer": true, - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, "webpack>events": true } }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "browserify>buffer": true, - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, "webpack>events": true } }, @@ -73,7 +69,12 @@ "TextEncoder": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "eth-lattice-keyring>@ethereumjs/tx>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": { "globals": { "TextEncoder": true } @@ -96,7 +97,7 @@ "@metamask/smart-transactions-controller>@ethereumjs/tx": { "packages": { "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true } @@ -105,7 +106,7 @@ "packages": { "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/rlp": true, + "eth-lattice-keyring>@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, "@ethersproject/providers": true, "browserify>buffer": true, @@ -115,14 +116,10 @@ }, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { "packages": { - "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethersproject/providers": true, - "browserify>buffer": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true, - "browserify>insert-module-globals>is-buffer": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, + "@ethereumjs/tx>ethereum-cryptography": true } }, "@ethereumjs/tx>@ethereumjs/util": { @@ -130,7 +127,7 @@ "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": true, "browserify>buffer": true, "@ethereumjs/tx>ethereum-cryptography": true, "webpack>events": true, @@ -138,13 +135,24 @@ "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true } }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": { + "globals": { + "console.warn": true, + "fetch": true + }, + "packages": { + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true + } + }, "@metamask/smart-transactions-controller>@ethereumjs/util": { "globals": { "console.warn": true, "fetch": true }, "packages": { - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "@ethereumjs/tx>ethereum-cryptography": true, "webpack>events": true } @@ -605,6 +613,19 @@ "process": true } }, + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": true, + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer>@json-schema-tools/traverse": true, + "@metamask/rpc-errors>fast-safe-stringify": true + } + }, + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver>@json-schema-spec/json-pointer": true, + "@open-rpc/test-coverage>isomorphic-fetch": true + } + }, "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { "packages": { "@ethereumjs/tx": true, @@ -612,7 +633,7 @@ "@keystonehq/bc-ur-registry-eth": true, "browserify>buffer": true, "@metamask/eth-trezor-keyring>hdkey": true, - "eth-lattice-keyring>rlp": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, "uuid": true } }, @@ -645,7 +666,7 @@ "@metamask/obs-store": true, "browserify>buffer": true, "webpack>events": true, - "@keystonehq/metamask-airgapped-keyring>rlp": true, + "ethereumjs-util>rlp": true, "uuid": true } }, @@ -1499,12 +1520,19 @@ } }, "@metamask/multichain": { + "globals": { + "console.error": true + }, "packages": { - "@metamask/multichain>@metamask/api-specs": true, + "@metamask/api-specs": true, "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-filters": true, "@metamask/permission-controller": true, "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, "@metamask/utils": true, + "@open-rpc/schema-utils-js": true, + "@metamask/multichain>jsonschema": true, "lodash": true } }, @@ -1778,7 +1806,7 @@ "@metamask/utils": true, "browserify>buffer": true, "webpack>events": true, - "@metamask/message-manager>jsonschema": true, + "@metamask/multichain>jsonschema": true, "uuid": true } }, @@ -2436,10 +2464,15 @@ "crypto": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "@open-rpc/schema-utils-js": { + "packages": { + "@open-rpc/schema-utils-js>@json-schema-tools/dereferencer": true, + "@open-rpc/schema-utils-js>@json-schema-tools/meta-schema": true, + "@open-rpc/schema-utils-js>@json-schema-tools/reference-resolver": true, + "@open-rpc/meta-schema": true, + "eslint>ajv": true, + "@metamask/rpc-errors>fast-safe-stringify": true, + "@open-rpc/schema-utils-js>is-url": true } }, "@popperjs/core": { @@ -2961,6 +2994,17 @@ "define": true } }, + "eslint>ajv": { + "globals": { + "console": true + }, + "packages": { + "eslint>fast-deep-equal": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "eslint>ajv>json-schema-traverse": true, + "uri-js": true + } + }, "chalk>ansi-styles": { "packages": { "chalk>ansi-styles>color-convert": true @@ -3145,11 +3189,6 @@ "define": true } }, - "eth-lattice-keyring>gridplus-sdk>bitwise": { - "packages": { - "browserify>buffer": true - } - }, "blo": { "globals": { "btoa": true @@ -3248,6 +3287,11 @@ "@ensdomains/content-hash>multihashes>multibase>base-x": true } }, + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": { + "packages": { + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58>base-x": true + } + }, "ethereumjs-util>ethereum-cryptography>bs58check": { "packages": { "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, @@ -3255,6 +3299,12 @@ "koa>content-disposition>safe-buffer": true } }, + "eth-lattice-keyring>gridplus-sdk>bs58check": { + "packages": { + "@noble/hashes": true, + "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": true + } + }, "buffer": { "globals": { "console": true @@ -3636,6 +3686,17 @@ "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, + "eth-lattice-keyring>gridplus-sdk>secp256k1>elliptic": { + "packages": { + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true + } + }, "string.prototype.matchall>call-bind>es-define-property": { "packages": { "string.prototype.matchall>get-intrinsic": true @@ -3655,16 +3716,6 @@ "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true } }, - "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": { - "globals": { - "intToBuffer": true - }, - "packages": { - "bn.js": true, - "buffer": true, - "@metamask/ethjs>js-sha3": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" @@ -3721,15 +3772,6 @@ "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { - "globals": { - "TextDecoder": true, - "crypto": true - }, - "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true - } - }, "ethereumjs-util>ethereum-cryptography": { "packages": { "browserify>buffer": true, @@ -3770,7 +3812,7 @@ "ethereumjs-util>create-hash": true, "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, "browserify>insert-module-globals>is-buffer": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true + "ethereumjs-util>rlp": true } }, "@metamask/keyring-controller>ethereumjs-wallet": { @@ -3966,24 +4008,23 @@ }, "packages": { "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, "@ethersproject/abi": true, + "@metamask/eth-sig-util": true, "eth-lattice-keyring>gridplus-sdk>aes-js": true, "@metamask/keyring-api>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>bitwise": true, "bn.js": true, "eth-lattice-keyring>gridplus-sdk>borc": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, + "eth-lattice-keyring>gridplus-sdk>bs58check": true, "browserify>buffer": true, "@ethereumjs/tx>@ethereumjs/common>crc-32": true, "eth-lattice-keyring>gridplus-sdk>elliptic": true, - "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, "ethers>@ethersproject/sha2>hash.js": true, "@metamask/ethjs>js-sha3": true, "lodash": true, - "eth-lattice-keyring>rlp": true, - "ganache>secp256k1": true, + "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true } }, @@ -4185,7 +4226,16 @@ "globals": { "URL": true, "URLSearchParams": true, - "location": true + "location": true, + "navigator": true + } + }, + "@open-rpc/test-coverage>isomorphic-fetch": { + "globals": { + "fetch.bind": true + }, + "packages": { + "@open-rpc/test-coverage>isomorphic-fetch>whatwg-fetch": true } }, "@ensdomains/content-hash>js-base64": { @@ -4214,7 +4264,7 @@ "define": true } }, - "@metamask/message-manager>jsonschema": { + "@metamask/multichain>jsonschema": { "packages": { "browserify>url": true } @@ -5206,10 +5256,9 @@ "pumpify>inherits": true } }, - "@keystonehq/metamask-airgapped-keyring>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { + "globals": { + "TextEncoder": true } }, "eth-lattice-keyring>rlp": { @@ -5223,12 +5272,6 @@ "browserify>buffer": true } }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, "wait-on>rxjs": { "globals": { "cancelAnimationFrame": true, @@ -5271,6 +5314,11 @@ "@metamask/ppom-validator>elliptic": true } }, + "eth-lattice-keyring>gridplus-sdk>secp256k1": { + "packages": { + "eth-lattice-keyring>gridplus-sdk>secp256k1>elliptic": true + } + }, "semver": { "globals": { "console.error": true @@ -5646,6 +5694,19 @@ "define": true } }, + "@open-rpc/test-coverage>isomorphic-fetch>whatwg-fetch": { + "globals": { + "AbortController": true, + "Blob": true, + "FileReader": true, + "FormData": true, + "URLSearchParams.prototype.isPrototypeOf": true, + "XMLHttpRequest": true, + "console.warn": true, + "define": true, + "setTimeout": true + } + }, "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { "packages": { "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, diff --git a/package.json b/package.json index 4bb941544c24..11a8645a4898 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "test:e2e:chrome:flask": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --build-type flask", "test:e2e:chrome:webpack": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js", "test:api-specs": "SELENIUM_BROWSER=chrome ts-node test/e2e/run-openrpc-api-test-coverage.ts", + "test:api-specs-multichain": "SELENIUM_BROWSER=chrome ts-node test/e2e/run-api-specs-multichain.ts", "test:e2e:mmi:ci": "yarn playwright test --project=mmi --project=mmi.visual", "test:e2e:mmi:all": "yarn playwright test --project=mmi && yarn test:e2e:mmi:visual", "test:e2e:mmi:regular": "yarn playwright test --project=mmi", @@ -144,7 +145,7 @@ "glob-parent": "^6.0.2", "netmask": "^2.0.1", "js-sha3": "^0.9.2", - "json-schema": "^0.4.0", + "jsonschema": "^1.4.1", "ast-types": "^0.14.2", "x-default-browser": "^0.5.2", "acorn@^7.0.0": "patch:acorn@npm:7.4.1#.yarn/patches/acorn-npm-7.4.1-f450b4646c.patch", @@ -325,7 +326,7 @@ "@metamask/message-manager": "^11.0.0", "@metamask/message-signing-snap": "^0.6.0", "@metamask/metamask-eth-abis": "^3.1.1", - "@metamask/multichain": "^2.0.0", + "@metamask/multichain": "npm:@metamask-previews/multichain@2.0.0-preview-2a983d27", "@metamask/name-controller": "^8.0.0", "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch", "@metamask/notification-services-controller": "^0.15.0", @@ -339,7 +340,7 @@ "@metamask/ppom-validator": "0.36.0", "@metamask/preinstalled-example-snap": "^0.3.0", "@metamask/profile-sync-controller": "^3.1.1", - "@metamask/providers": "^18.2.0", + "@metamask/providers": "^18.3.0", "@metamask/queued-request-controller": "^7.0.1", "@metamask/rate-limit-controller": "^6.0.0", "@metamask/remote-feature-flag-controller": "^1.1.0", @@ -463,7 +464,7 @@ "@lavamoat/lavapack": "^7.0.5", "@lgbot/madge": "^6.2.0", "@lydell/node-pty": "^1.0.1", - "@metamask/api-specs": "^0.9.3", + "@metamask/api-specs": "^0.10.12", "@metamask/auto-changelog": "^2.1.0", "@metamask/build-utils": "^3.0.0", "@metamask/eslint-config": "^9.0.0", @@ -481,8 +482,8 @@ "@octokit/core": "^3.6.0", "@open-rpc/meta-schema": "^1.14.6", "@open-rpc/mock-server": "^1.7.5", - "@open-rpc/schema-utils-js": "^1.16.2", - "@open-rpc/test-coverage": "^2.2.2", + "@open-rpc/schema-utils-js": "^2.0.5", + "@open-rpc/test-coverage": "^2.2.4", "@playwright/test": "^1.39.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@sentry/cli": "^2.19.4", diff --git a/shared/constants/app.ts b/shared/constants/app.ts index 12b340d35fa5..64030e5da4a4 100644 --- a/shared/constants/app.ts +++ b/shared/constants/app.ts @@ -47,6 +47,11 @@ export const MESSAGE_TYPE = { TRANSACTION: 'transaction', WALLET_REQUEST_PERMISSIONS: 'wallet_requestPermissions', WATCH_ASSET: 'wallet_watchAsset', + WALLET_CREATE_SESSION: 'wallet_createSession', + WALLET_GET_SESSION: 'wallet_getSession', + WALLET_INVOKE_METHOD: 'wallet_invokeMethod', + WALLET_REVOKE_SESSION: 'wallet_revokeSession', + WALLET_SESSION_CHANGED: 'wallet_sessionChanged', WATCH_ASSET_LEGACY: 'metamask_watchAsset', SNAP_DIALOG_ALERT: DIALOG_APPROVAL_TYPES.alert, SNAP_DIALOG_CONFIRMATION: DIALOG_APPROVAL_TYPES.confirmation, diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 787322d79dac..1f78158ab834 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -974,11 +974,11 @@ export const CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP = { * Ethereum JSON-RPC methods that are known to exist but that we intentionally * do not support. */ -export const UNSUPPORTED_RPC_METHODS = new Set([ +export const UNSUPPORTED_RPC_METHODS = [ // This is implemented later in our middleware stack – specifically, in // eth-json-rpc-middleware – but our UI does not support it. 'eth_signTransaction' as const, -]); +]; export const IPFS_DEFAULT_GATEWAY_URL = 'dweb.link'; diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index d97a18bda992..37fed9d693d5 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -77,5 +77,20 @@ describe('CAIP Stream', () => { { type: 'caip-x', data: { foo: 'bar' } }, ]); }); + + it('ends the substream when the source stream ends', async () => { + // using a fake stream here instead of PassThrough to prevent a loop + // when sourceStream gets written back to at the end of the CAIP pipeline + const sourceStream = new MockStream(); + + const providerStream = createCaipStream(sourceStream); + + const { promise, resolve } = createDeferredPromise(); + providerStream.on('close', () => resolve?.()); + + sourceStream.destroy(); + + await expect(promise).resolves.toBe(undefined); + }); }); }); diff --git a/shared/modules/caip-stream.ts b/shared/modules/caip-stream.ts index 3f13927efc27..09e0891bc3d6 100644 --- a/shared/modules/caip-stream.ts +++ b/shared/modules/caip-stream.ts @@ -65,9 +65,10 @@ export class CaipStream extends Duplex { export const createCaipStream = (portStream: Duplex): Duplex => { const caipStream = new CaipStream(); - pipeline(portStream, caipStream, portStream, (err: Error) => - console.log('MetaMask CAIP stream', err), - ); + pipeline(portStream, caipStream, portStream, (err: Error) => { + caipStream.substream.destroy(); + console.log('MetaMask CAIP stream', err); + }); return caipStream.substream; }; diff --git a/test/e2e/api-specs/ConfirmationRejectionRule.ts b/test/e2e/api-specs/ConfirmationRejectionRule.ts index 43046d8b0943..cbb23ac834c2 100644 --- a/test/e2e/api-specs/ConfirmationRejectionRule.ts +++ b/test/e2e/api-specs/ConfirmationRejectionRule.ts @@ -99,8 +99,6 @@ export class ConfirmationsRejectRule implements Rule { await this.driver.executeScript( `window.ethereum.request(${switchEthereumChainRequest})`, ); - - await switchToOrOpenDapp(this.driver); } } catch (e) { console.log(e); diff --git a/test/e2e/api-specs/MultichainAuthorizationConfirmation.ts b/test/e2e/api-specs/MultichainAuthorizationConfirmation.ts new file mode 100644 index 000000000000..c7286257fad2 --- /dev/null +++ b/test/e2e/api-specs/MultichainAuthorizationConfirmation.ts @@ -0,0 +1,125 @@ +import Rule from '@open-rpc/test-coverage/build/rules/rule'; +import { Call } from '@open-rpc/test-coverage/build/coverage'; +import { + ContentDescriptorObject, + ExampleObject, + ExamplePairingObject, + MethodObject, +} from '@open-rpc/meta-schema'; +import paramsToObj from '@open-rpc/test-coverage/build/utils/params-to-obj'; +import _ from 'lodash'; +import { Driver } from '../webdriver/driver'; +import { WINDOW_TITLES, switchToOrOpenDapp } from '../helpers'; +import { addToQueue } from './helpers'; + +type MultichainAuthorizationConfirmationOptions = { + driver: Driver; + only?: string[]; +}; +// this rule makes sure that a multichain authorization confirmation dialog is shown and confirmed +export class MultichainAuthorizationConfirmation implements Rule { + private driver: Driver; + + private only: string[]; + + constructor(options: MultichainAuthorizationConfirmationOptions) { + this.driver = options.driver; + this.only = options.only || ['wallet_createSession']; + } + + getTitle() { + return 'Multichain Authorization Confirmation Rule'; + } + + async afterRequest(__: unknown, call: Call) { + await new Promise((resolve, reject) => { + addToQueue({ + name: 'afterRequest', + resolve, + reject, + task: async () => { + try { + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const text = 'Connect'; + + await this.driver.findClickableElements({ + text, + tag: 'button', + }); + + const screenshot = await this.driver.driver.takeScreenshot(); + call.attachments = call.attachments || []; + call.attachments.push({ + type: 'image', + data: `data:image/png;base64,${screenshot}`, + }); + await this.driver.clickElement({ text, tag: 'button' }); + + // make sure to switch back to the dapp or else the next test will fail on the wrong window + await switchToOrOpenDapp(this.driver); + } catch (e) { + console.log(e); + } + }, + }); + }); + } + + // get all the confirmation calls to make and expect to pass + getCalls(__: unknown, method: MethodObject) { + const calls: Call[] = []; + const isMethodAllowed = this.only ? this.only.includes(method.name) : true; + if (isMethodAllowed) { + if (method.examples) { + // pull the first example + const e = method.examples[0]; + const ex = e as ExamplePairingObject; + + if (!ex.result) { + return calls; + } + const p = ex.params.map((_e) => (_e as ExampleObject).value); + const params = + method.paramStructure === 'by-name' + ? paramsToObj(p, method.params as ContentDescriptorObject[]) + : p; + calls.push({ + title: `${this.getTitle()} - with example ${ex.name}`, + methodName: method.name, + params, + url: '', + resultSchema: (method.result as ContentDescriptorObject).schema, + expectedResult: (ex.result as ExampleObject).value, + }); + } else { + // naively call the method with no params + calls.push({ + title: `${method.name} > multichain authorization confirmation`, + methodName: method.name, + params: [], + url: '', + resultSchema: (method.result as ContentDescriptorObject).schema, + }); + } + } + return calls; + } + + validateCall(call: Call) { + if (call.error) { + call.valid = false; + call.reason = `Expected a result but got error \ncode: ${call.error.code}\n message: ${call.error.message}`; + } else { + call.valid = _.isEqual(call.result, call.expectedResult); + if (!call.valid) { + call.reason = `Expected:\n${JSON.stringify( + call.expectedResult, + null, + 4, + )} but got\n${JSON.stringify(call.result, null, 4)}`; + } + } + return call; + } +} diff --git a/test/e2e/api-specs/MultichainAuthorizationConfirmationErrors.ts b/test/e2e/api-specs/MultichainAuthorizationConfirmationErrors.ts new file mode 100644 index 000000000000..5df26137125d --- /dev/null +++ b/test/e2e/api-specs/MultichainAuthorizationConfirmationErrors.ts @@ -0,0 +1,140 @@ +import Rule from '@open-rpc/test-coverage/build/rules/rule'; +import { Call } from '@open-rpc/test-coverage/build/coverage'; +import { + ContentDescriptorObject, + ErrorObject, + MethodObject, +} from '@open-rpc/meta-schema'; +import _ from 'lodash'; +import { Driver } from '../webdriver/driver'; +import { WINDOW_TITLES, switchToOrOpenDapp } from '../helpers'; +import { addToQueue } from './helpers'; + +type MultichainAuthorizationConfirmationOptions = { + driver: Driver; + only?: string[]; +}; +// this rule makes sure that a multichain authorization error codes are returned +export class MultichainAuthorizationConfirmationErrors implements Rule { + private driver: Driver; + + private only: string[]; + + private errorCodesToHitCancel: number[]; + + constructor(options: MultichainAuthorizationConfirmationOptions) { + this.driver = options.driver; + this.only = options.only || ['wallet_createSession']; + this.errorCodesToHitCancel = [5001, 5002]; + } + + getTitle() { + return 'Multichain Authorization Confirmation Rule'; + } + + async afterRequest(__: unknown, call: Call) { + await new Promise((resolve, reject) => { + addToQueue({ + name: 'afterRequest', + resolve, + reject, + task: async () => { + if (this.errorCodesToHitCancel.includes(call.expectedResult?.code)) { + try { + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const text = 'Cancel'; + + await this.driver.findClickableElements({ + text: 'Cancel', + tag: 'button', + }); + + const screenshot = await this.driver.driver.takeScreenshot(); + call.attachments = call.attachments || []; + call.attachments.push({ + type: 'image', + data: `data:image/png;base64,${screenshot}`, + }); + await this.driver.clickElement({ text, tag: 'button' }); + // make sure to switch back to the dapp or else the next test will fail on the wrong window + await switchToOrOpenDapp(this.driver); + } catch (e) { + console.log(e); + } + } + }, + }); + }); + } + + getCalls(__: unknown, method: MethodObject) { + const calls: Call[] = []; + const isMethodAllowed = this.only ? this.only.includes(method.name) : true; + if (isMethodAllowed) { + if (method.errors) { + method.errors.forEach((err) => { + const unsupportedErrorCodes = [5000, 5100, 5101, 5102, 5300, 5301]; + const error = err as ErrorObject; + if (unsupportedErrorCodes.includes(error.code)) { + return; + } + let params: Record = {}; + switch (error.code) { + case 5100: + params = { + requiredScopes: { + 'eip155:10124': { + methods: ['eth_signTypedData_v4'], + notifications: [], + }, + }, + }; + break; + case 5302: + params = { + requiredScopes: { + 'eip155:1': { + methods: ['eth_signTypedData_v4'], + notifications: [], + }, + }, + sessionProperties: {}, + }; + break; + default: + break; + } + + // params should make error happen (or lifecycle hooks will make it happen) + calls.push({ + title: `${this.getTitle()} - with error ${error.code} ${ + error.message + } `, + methodName: method.name, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params: params as any, + url: '', + resultSchema: (method.result as ContentDescriptorObject).schema, + expectedResult: error, + }); + }); + } + } + return calls; + } + + validateCall(call: Call) { + if (call.error) { + call.valid = _.isEqual(call.error.code, call.expectedResult.code); + if (!call.valid) { + call.reason = `Expected:\n${JSON.stringify( + call.expectedResult, + null, + 4, + )} but got\n${JSON.stringify(call.error, null, 4)}`; + } + } + return call; + } +} diff --git a/test/e2e/api-specs/helpers.ts b/test/e2e/api-specs/helpers.ts index 51cdbbe47951..7febfdafaa05 100644 --- a/test/e2e/api-specs/helpers.ts +++ b/test/e2e/api-specs/helpers.ts @@ -1,5 +1,7 @@ import { v4 as uuid } from 'uuid'; import { ErrorObject } from '@open-rpc/meta-schema'; +import { Json, JsonRpcFailure, JsonRpcResponse } from '@metamask/utils'; +import { InternalScopeString } from '@metamask/multichain'; import { Driver } from '../webdriver/driver'; // eslint-disable-next-line @typescript-eslint/no-shadow, @typescript-eslint/no-explicit-any @@ -47,7 +49,6 @@ export const pollForResult = async ( generatedKey: string, ): Promise => { let result; - // eslint-disable-next-line no-loop-func await new Promise((resolve, reject) => { addToQueue({ name: 'pollResult', @@ -58,7 +59,7 @@ export const pollForResult = async ( `return window['${generatedKey}'];`, ); - if (result) { + if (result !== undefined && result !== null) { // clear the result await driver.executeScript(`delete window['${generatedKey}'];`); } else { @@ -75,9 +76,172 @@ export const pollForResult = async ( return pollForResult(driver, generatedKey); }; +export const createCaip27DriverTransport = ( + driver: Driver, + scopeMap: Record, + extensionId: string, +) => { + // use externally_connectable to communicate with the extension + // https://developer.chrome.com/docs/extensions/mv3/messaging/ + return async ( + __: string, + method: string, + params: unknown[] | Record, + ) => { + const generatedKey = uuid(); + addToQueue({ + name: 'transport', + resolve: () => { + // noop + }, + reject: () => { + // noop + }, + task: async () => { + // don't wait for executeScript to finish window.ethereum promise + // we need this because if we wait for the promise to resolve it + // will hang in selenium since it can only do one thing at a time. + // the workaround is to put the response on window.asyncResult and poll for it. + driver.executeScript( + ([m, p, g, s, e]: [ + string, + unknown[] | Record, + string, + InternalScopeString, + string, + ]) => { + const extensionPort = chrome.runtime.connect(e); + + const listener = ({ + type, + data, + }: { + type: string; + data: JsonRpcResponse; + }) => { + if (type !== 'caip-x') { + return; + } + if (data?.id !== g) { + return; + } + + if (data.id || (data as JsonRpcFailure).error) { + window[g] = data; + extensionPort.onMessage.removeListener(listener); + } + }; + + extensionPort.onMessage.addListener(listener); + const msg = { + type: 'caip-x', + data: { + jsonrpc: '2.0', + method: 'wallet_invokeMethod', + params: { + request: { + method: m, + params: p, + }, + scope: s, + }, + id: g, + }, + }; + extensionPort.postMessage(msg); + }, + method, + params, + generatedKey, + scopeMap[method], + extensionId, + ); + }, + }); + return pollForResult(driver, generatedKey); + }; +}; + +export const createMultichainDriverTransport = ( + driver: Driver, + extensionId: string, +) => { + // use externally_connectable to communicate with the extension + // https://developer.chrome.com/docs/extensions/mv3/messaging/ + return async ( + __: string, + method: string, + params: unknown[] | Record, + ) => { + const generatedKey = uuid(); + addToQueue({ + name: 'transport', + resolve: () => { + // noop + }, + reject: () => { + // noop + }, + task: async () => { + // don't wait for executeScript to finish window.ethereum promise + // we need this because if we wait for the promise to resolve it + // will hang in selenium since it can only do one thing at a time. + // the workaround is to put the response on window.asyncResult and poll for it. + driver.executeScript( + ([m, p, g, e]: [ + string, + unknown[] | Record, + string, + string, + ]) => { + const extensionPort = chrome.runtime.connect(e); + + const listener = ({ + type, + data, + }: { + type: string; + data: JsonRpcResponse; + }) => { + if (type !== 'caip-x') { + return; + } + if (data?.id !== g) { + return; + } + + if (data.id || (data as JsonRpcFailure).error) { + window[g] = data; + extensionPort.onMessage.removeListener(listener); + } + }; + + extensionPort.onMessage.addListener(listener); + const msg = { + type: 'caip-x', + data: { + jsonrpc: '2.0', + method: m, + params: p, + id: g, + }, + }; + extensionPort.postMessage(msg); + }, + method, + params, + generatedKey, + extensionId, + ); + }, + }); + return pollForResult(driver, generatedKey); + }; +}; + export const createDriverTransport = (driver: Driver) => { return async ( - _: string, + __: string, method: string, params: unknown[] | Record, ) => { @@ -109,6 +273,7 @@ export const createDriverTransport = (driver: Driver) => { }) .catch((e: ErrorObject) => { window[g] = { + id: g, error: { code: e.code, message: e.message, diff --git a/test/e2e/api-specs/transform.ts b/test/e2e/api-specs/transform.ts new file mode 100644 index 000000000000..ccbd696d407c --- /dev/null +++ b/test/e2e/api-specs/transform.ts @@ -0,0 +1,334 @@ +import { + ExampleObject, + ExamplePairingObject, + MethodObject, + OpenrpcDocument, +} from '@open-rpc/meta-schema'; + +const transformOpenRPCDocument = ( + openrpcDocument: OpenrpcDocument, + chainId: number, + account: string, +): [OpenrpcDocument, string[], string[]] => { + // transform the document here + + const transaction = + openrpcDocument.components?.schemas?.TransactionInfo?.allOf?.[0]; + + if (transaction) { + delete transaction.unevaluatedProperties; + } + + const chainIdMethod = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_chainId', + ); + (chainIdMethod as MethodObject).examples = [ + { + name: 'chainIdExample', + description: 'Example of a chainId request', + params: [], + result: { + name: 'chainIdResult', + value: `0x${chainId.toString(16)}`, + }, + }, + ]; + + const getBalanceMethod = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_getBalance', + ); + + (getBalanceMethod as MethodObject).examples = [ + { + name: 'getBalanceExample', + description: 'Example of a getBalance request', + params: [ + { + name: 'address', + value: account, + }, + { + name: 'tag', + value: 'latest', + }, + ], + result: { + name: 'getBalanceResult', + value: '0x1a8819e0c9bab700', // can we get this from a variable too + }, + }, + ]; + + const blockNumber = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_blockNumber', + ); + + (blockNumber as MethodObject).examples = [ + { + name: 'blockNumberExample', + description: 'Example of a blockNumber request', + params: [], + result: { + name: 'blockNumberResult', + value: '0x1', + }, + }, + ]; + + const personalSign = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'personal_sign', + ); + + (personalSign as MethodObject).examples = [ + { + name: 'personalSignExample', + description: 'Example of a personalSign request', + params: [ + { + name: 'data', + value: '0xdeadbeef', + }, + { + name: 'address', + value: account, + }, + ], + result: { + name: 'personalSignResult', + value: '0x1a8819e0c9bab700', + }, + }, + ]; + + const switchEthereumChain = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'wallet_switchEthereumChain', + ); + (switchEthereumChain as MethodObject).examples = [ + { + name: 'wallet_switchEthereumChain', + description: 'Example of a wallet_switchEthereumChain request to sepolia', + params: [ + { + name: 'SwitchEthereumChainParameter', + value: { + chainId: '0xaa36a7', + }, + }, + ], + result: { + name: 'wallet_switchEthereumChain', + value: null, + }, + }, + ]; + + const getProof = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_getProof', + ); + + // delete invalid example until its fixed here: https://github.com/ethereum/execution-apis/pull/588 + ( + ((getProof as MethodObject).examples?.[0] as ExamplePairingObject) + ?.params[1] as ExampleObject + ).value.pop(); + + const signTypedData4 = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_signTypedData_v4', + ); + + const signTypedData4Example = (signTypedData4 as MethodObject) + .examples?.[0] as ExamplePairingObject; + + // just update address for signTypedData + (signTypedData4Example.params[0] as ExampleObject).value = account; + + // update chainId for signTypedData + (signTypedData4Example.params[1] as ExampleObject).value.domain.chainId = + chainId; + + // net_version missing from execution-apis. see here: https://github.com/ethereum/execution-apis/issues/540 + const netVersion: MethodObject = { + name: 'net_version', + summary: 'Returns the current network ID.', + params: [], + result: { + description: 'Returns the current network ID.', + name: 'net_version', + schema: { + type: 'string', + }, + }, + description: 'Returns the current network ID.', + examples: [ + { + name: 'net_version', + description: 'Example of a net_version request', + params: [], + result: { + name: 'net_version', + description: 'The current network ID', + value: '0x1', + }, + }, + ], + }; + // add net_version + (openrpcDocument.methods as MethodObject[]).push( + netVersion as unknown as MethodObject, + ); + + const getEncryptionPublicKey = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_getEncryptionPublicKey', + ); + + (getEncryptionPublicKey as MethodObject).examples = [ + { + name: 'getEncryptionPublicKeyExample', + description: 'Example of a getEncryptionPublicKey request', + params: [ + { + name: 'address', + value: account, + }, + ], + result: { + name: 'getEncryptionPublicKeyResult', + value: '0x1a8819e0c9bab700', + }, + }, + ]; + + const getTransactionCount = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_getTransactionCount', + ); + (getTransactionCount as MethodObject).examples = [ + { + name: 'getTransactionCountExampleEarliest', + description: 'Example of a pending getTransactionCount request', + params: [ + { + name: 'address', + value: account, + }, + { + name: 'tag', + value: 'earliest', + }, + ], + result: { + name: 'getTransactionCountResult', + value: '0x0', + }, + }, + { + name: 'getTransactionCountExampleFinalized', + description: 'Example of a pending getTransactionCount request', + params: [ + { + name: 'address', + value: account, + }, + { + name: 'tag', + value: 'finalized', + }, + ], + result: { + name: 'getTransactionCountResult', + value: '0x0', + }, + }, + { + name: 'getTransactionCountExampleSafe', + description: 'Example of a pending getTransactionCount request', + params: [ + { + name: 'address', + value: account, + }, + { + name: 'tag', + value: 'safe', + }, + ], + result: { + name: 'getTransactionCountResult', + value: '0x0', + }, + }, + { + name: 'getTransactionCountExample', + description: 'Example of a getTransactionCount request', + params: [ + { + name: 'address', + value: account, + }, + { + name: 'tag', + value: 'latest', + }, + ], + result: { + name: 'getTransactionCountResult', + value: '0x0', + }, + }, + // returns a number right now. see here: https://github.com/MetaMask/metamask-extension/pull/14822 + // { + // name: 'getTransactionCountExamplePending', + // description: 'Example of a pending getTransactionCount request', + // params: [ + // { + // name: 'address', + // value: account, + // }, + // { + // name: 'tag', + // value: 'pending', + // }, + // ], + // result: { + // name: 'getTransactionCountResult', + // value: '0x0', + // }, + // }, + ]; + // TODO: move these to a "Confirmation" tag in api-specs + const methodsWithConfirmations = [ + 'wallet_requestPermissions', + 'eth_requestAccounts', + 'wallet_watchAsset', + 'personal_sign', // requires permissions for eth_accounts + 'wallet_addEthereumChain', + 'eth_signTypedData_v4', // requires permissions for eth_accounts + 'wallet_switchEthereumChain', + + // commented out because its not returning 4001 error. + // see here https://github.com/MetaMask/metamask-extension/issues/24227 + // 'eth_getEncryptionPublicKey', // requires permissions for eth_accounts + ]; + const filteredMethods = openrpcDocument.methods + .filter((_m: unknown) => { + const m = _m as MethodObject; + return ( + m.name.includes('snap') || + m.name.includes('Snap') || + m.name.toLowerCase().includes('account') || + m.name.includes('crypt') || + m.name.includes('blob') || + m.name.includes('sendTransaction') || + m.name.startsWith('wallet_scanQRCode') || + methodsWithConfirmations.includes(m.name) || + // filters are currently 0 prefixed for odd length on + // extension which doesn't pass spec + // see here: https://github.com/MetaMask/eth-json-rpc-filters/issues/152 + m.name.includes('filter') || + m.name.includes('Filter') + ); + }) + .map((m) => (m as MethodObject).name); + return [openrpcDocument, filteredMethods, methodsWithConfirmations]; +}; + +export default transformOpenRPCDocument; diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 3b69d55fc122..3a46b16b6f25 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -49,6 +49,7 @@ const convertETHToHexGwei = (eth) => convertToHexValue(eth * 10 ** 18); * @property {Bundler} bundlerServer - The bundler server. * @property {mockttp.Mockttp} mockServer - The mock server. * @property {object} manifestFlags - Flags to add to the manifest in order to change things at runtime. + * @property {string} extensionId - the ID that the extension can be found at via externally_connectable. */ /** @@ -97,9 +98,11 @@ async function withFixtures(options, testSuite) { getServerMochaToBackground(); } - let webDriver; let driver; + let webDriver; + let extensionId; let failed = false; + try { if (!disableGanache) { await ganacheServer.start(ganacheOptions); @@ -187,7 +190,9 @@ async function withFixtures(options, testSuite) { await setManifestFlags(manifestFlags); - driver = (await buildWebDriver(driverOptions)).driver; + const wd = await buildWebDriver(driverOptions); + driver = wd.driver; + extensionId = wd.extensionId; webDriver = driver.driver; if (process.env.SELENIUM_BROWSER === 'chrome') { @@ -225,6 +230,7 @@ async function withFixtures(options, testSuite) { mockedEndpoint, bundlerServer, mockServer, + extensionId, }); const errorsAndExceptions = driver.summarizeErrorsAndExceptions(); diff --git a/test/e2e/run-api-specs-multichain.ts b/test/e2e/run-api-specs-multichain.ts new file mode 100644 index 000000000000..8ac7326b6902 --- /dev/null +++ b/test/e2e/run-api-specs-multichain.ts @@ -0,0 +1,279 @@ +import testCoverage from '@open-rpc/test-coverage'; +import { parseOpenRPCDocument } from '@open-rpc/schema-utils-js'; +import HtmlReporter from '@open-rpc/test-coverage/build/reporters/html-reporter'; +import { + MultiChainOpenRPCDocument, + MetaMaskOpenRPCDocument, +} from '@metamask/api-specs'; + +import { MethodObject, OpenrpcDocument } from '@open-rpc/meta-schema'; +import JsonSchemaFakerRule from '@open-rpc/test-coverage/build/rules/json-schema-faker-rule'; +import ExamplesRule from '@open-rpc/test-coverage/build/rules/examples-rule'; +import { IOptions } from '@open-rpc/test-coverage/build/coverage'; +import { InternalScopeString } from '@metamask/multichain'; +import { Driver, PAGES } from './webdriver/driver'; + +import { + createCaip27DriverTransport, + createMultichainDriverTransport, +} from './api-specs/helpers'; + +import FixtureBuilder from './fixture-builder'; +import { + withFixtures, + openDapp, + unlockWallet, + DAPP_URL, + ACCOUNT_1, +} from './helpers'; +import { MultichainAuthorizationConfirmation } from './api-specs/MultichainAuthorizationConfirmation'; +import transformOpenRPCDocument from './api-specs/transform'; +import { MultichainAuthorizationConfirmationErrors } from './api-specs/MultichainAuthorizationConfirmationErrors'; +import { ConfirmationsRejectRule } from './api-specs/ConfirmationRejectionRule'; + +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const mockServer = require('@open-rpc/mock-server/build/index').default; + +async function main() { + const port = 8545; + const chainId = 1337; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + disableGanache: true, + title: 'api-specs coverage', + }, + async ({ + driver, + extensionId, + }: { + driver: Driver; + extensionId: string; + }) => { + await unlockWallet(driver); + + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); + + // Open Dapp + await openDapp(driver, undefined, DAPP_URL); + + const doc = await parseOpenRPCDocument( + MultiChainOpenRPCDocument as OpenrpcDocument, + ); + const providerAuthorize = doc.methods.find( + (m) => (m as MethodObject).name === 'wallet_createSession', + ); + + const walletRpcMethods: string[] = [ + 'wallet_registerOnboarding', + 'wallet_scanQRCode', + ]; + const walletEip155Methods = ['wallet_addEthereumChain']; + + const ignoreMethods = [ + 'wallet_switchEthereumChain', + 'wallet_getPermissions', + 'wallet_requestPermissions', + 'wallet_revokePermissions', + 'eth_requestAccounts', + 'eth_accounts', + 'eth_coinbase', + 'net_version', + ]; + + const transport = createMultichainDriverTransport(driver, extensionId); + const [transformedDoc, filteredMethods, methodsWithConfirmations] = + transformOpenRPCDocument( + MetaMaskOpenRPCDocument as OpenrpcDocument, + chainId, + ACCOUNT_1, + ); + const ethereumMethods = transformedDoc.methods + .map((m) => (m as MethodObject).name) + .filter((m) => { + const match = + walletRpcMethods.includes(m) || + walletEip155Methods.includes(m) || + ignoreMethods.includes(m); + return !match; + }); + const confirmationMethods = methodsWithConfirmations.filter( + (m) => !ignoreMethods.includes(m), + ); + const scopeMap: Record = { + [`eip155:${chainId}`]: ethereumMethods, + 'wallet:eip155': walletEip155Methods, + wallet: walletRpcMethods, + }; + + const reverseScopeMap = Object.entries(scopeMap).reduce( + (acc, [scope, methods]: [string, string[]]) => { + methods.forEach((method) => { + acc[method] = scope; + }); + return acc; + }, + {} as { [method: string]: string }, + ); + + // fix the example for wallet_createSession + (providerAuthorize as MethodObject).examples = [ + { + name: 'wallet_createSessionExample', + description: 'Example of a provider authorization request.', + params: [ + { + name: 'requiredScopes', + value: { + eip155: { + references: ['1337'], + methods: ethereumMethods, + notifications: ['eth_subscription'], + }, + 'wallet:eip155': { + methods: walletEip155Methods, + notifications: [], + }, + wallet: { + methods: walletRpcMethods, + notifications: [], + }, + }, + }, + ], + result: { + name: 'wallet_createSessionResultExample', + value: { + sessionScopes: { + [`eip155:${chainId}`]: { + accounts: [`eip155:${chainId}:${ACCOUNT_1}`], + methods: ethereumMethods, + notifications: ['eth_subscription'], + }, + 'wallet:eip155': { + accounts: [`wallet:eip155:${ACCOUNT_1}`], + methods: walletEip155Methods, + notifications: [], + }, + wallet: { + accounts: [`wallet:eip155:${ACCOUNT_1}`], + methods: walletRpcMethods, + notifications: [], + }, + }, + }, + }, + }, + ]; + + const server = mockServer( + port, + await parseOpenRPCDocument(transformedDoc), + ); + server.start(); + + const getSession = doc.methods.find( + (m) => (m as MethodObject).name === 'wallet_getSession', + ); + (getSession as MethodObject).examples = [ + { + name: 'wallet_getSessionExample', + description: 'Example of a provider authorization request.', + params: [], + result: { + name: 'wallet_getSessionResultExample', + value: { + sessionScopes: {}, + }, + }, + }, + ]; + + const testCoverageResults = await testCoverage({ + openrpcDocument: doc, + transport, + reporters: ['console-streaming'], + skip: ['wallet_invokeMethod'], + rules: [ + new ExamplesRule({ + skip: [], + only: ['wallet_getSession', 'wallet_revokeSession'], + }), + new MultichainAuthorizationConfirmation({ + driver, + }), + new MultichainAuthorizationConfirmationErrors({ + driver, + }), + ], + }); + + const testCoverageResultsCaip27 = await testCoverage({ + openrpcDocument: MetaMaskOpenRPCDocument as OpenrpcDocument, + transport: createCaip27DriverTransport( + driver, + reverseScopeMap, + extensionId, + ), + reporters: ['console-streaming'], + skip: [ + 'eth_coinbase', + 'wallet_revokePermissions', + 'wallet_requestPermissions', + 'wallet_getPermissions', + 'eth_accounts', + 'eth_requestAccounts', + 'net_version', // not in the spec yet for some reason + // these 2 methods below are not supported by MetaMask extension yet and + // don't get passed through. See here: https://github.com/MetaMask/metamask-extension/issues/24225 + 'eth_getBlockReceipts', + 'eth_maxPriorityFeePerGas', + ], + rules: [ + new JsonSchemaFakerRule({ + only: [], + skip: filteredMethods, + numCalls: 2, + }), + new ExamplesRule({ + only: [], + skip: filteredMethods, + }), + new ConfirmationsRejectRule({ + driver, + only: confirmationMethods, + }), + ], + }); + + const joinedResults = testCoverageResults.concat( + testCoverageResultsCaip27, + ); + + // fix ids for html reporter + joinedResults.forEach((r, index) => { + r.id = index; + }); + + const htmlReporter = new HtmlReporter({ + autoOpen: !process.env.CI, + destination: `${process.cwd()}/html-report-multichain`, + }); + + await htmlReporter.onEnd({} as IOptions, joinedResults); + + await driver.quit(); + + // if any of the tests failed, exit with a non-zero code + if (joinedResults.every((r) => r.valid)) { + process.exit(0); + } else { + process.exit(1); + } + }, + ); +} + +main(); diff --git a/test/e2e/run-openrpc-api-test-coverage.ts b/test/e2e/run-openrpc-api-test-coverage.ts index f192f6088954..a48ac4237e68 100644 --- a/test/e2e/run-openrpc-api-test-coverage.ts +++ b/test/e2e/run-openrpc-api-test-coverage.ts @@ -4,12 +4,8 @@ import HtmlReporter from '@open-rpc/test-coverage/build/reporters/html-reporter' import ExamplesRule from '@open-rpc/test-coverage/build/rules/examples-rule'; import JsonSchemaFakerRule from '@open-rpc/test-coverage/build/rules/json-schema-faker-rule'; -import { - ExampleObject, - ExamplePairingObject, - MethodObject, -} from '@open-rpc/meta-schema'; -import openrpcDocument from '@metamask/api-specs'; +import { OpenrpcDocument } from '@open-rpc/meta-schema'; +import { MetaMaskOpenRPCDocument } from '@metamask/api-specs'; import { ConfirmationsRejectRule } from './api-specs/ConfirmationRejectionRule'; import { Driver, PAGES } from './webdriver/driver'; @@ -24,6 +20,7 @@ import { DAPP_URL, ACCOUNT_1, } from './helpers'; +import transformOpenRPCDocument from './api-specs/transform'; // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires const mockServer = require('@open-rpc/mock-server/build/index').default; @@ -48,324 +45,19 @@ async function main() { await openDapp(driver, undefined, DAPP_URL); const transport = createDriverTransport(driver); - - const transaction = - openrpcDocument.components?.schemas?.TransactionInfo?.allOf?.[0]; - - if (transaction) { - delete transaction.unevaluatedProperties; - } - - const chainIdMethod = openrpcDocument.methods.find( - (m) => (m as MethodObject).name === 'eth_chainId', - ); - (chainIdMethod as MethodObject).examples = [ - { - name: 'chainIdExample', - description: 'Example of a chainId request', - params: [], - result: { - name: 'chainIdResult', - value: `0x${chainId.toString(16)}`, - }, - }, - ]; - - const getBalanceMethod = openrpcDocument.methods.find( - (m) => (m as MethodObject).name === 'eth_getBalance', - ); - - (getBalanceMethod as MethodObject).examples = [ - { - name: 'getBalanceExample', - description: 'Example of a getBalance request', - params: [ - { - name: 'address', - value: ACCOUNT_1, - }, - { - name: 'tag', - value: 'latest', - }, - ], - result: { - name: 'getBalanceResult', - value: '0x1a8819e0c9bab700', // can we get this from a variable too - }, - }, - ]; - - const blockNumber = openrpcDocument.methods.find( - (m) => (m as MethodObject).name === 'eth_blockNumber', - ); - - (blockNumber as MethodObject).examples = [ - { - name: 'blockNumberExample', - description: 'Example of a blockNumber request', - params: [], - result: { - name: 'blockNumberResult', - value: '0x1', - }, - }, - ]; - - const personalSign = openrpcDocument.methods.find( - (m) => (m as MethodObject).name === 'personal_sign', - ); - - (personalSign as MethodObject).examples = [ - { - name: 'personalSignExample', - description: 'Example of a personalSign request', - params: [ - { - name: 'data', - value: '0xdeadbeef', - }, - { - name: 'address', - value: ACCOUNT_1, - }, - ], - result: { - name: 'personalSignResult', - value: '0x1a8819e0c9bab700', - }, - }, - ]; - - const switchEthereumChain = openrpcDocument.methods.find( - (m) => (m as MethodObject).name === 'wallet_switchEthereumChain', - ); - (switchEthereumChain as MethodObject).examples = [ - { - name: 'wallet_switchEthereumChain', - description: - 'Example of a wallet_switchEthereumChain request to sepolia', - params: [ - { - name: 'SwitchEthereumChainParameter', - value: { - chainId: '0xaa36a7', - }, - }, - ], - result: { - name: 'wallet_switchEthereumChain', - value: null, - }, - }, - ]; - - const signTypedData4 = openrpcDocument.methods.find( - (m) => (m as MethodObject).name === 'eth_signTypedData_v4', - ); - - const signTypedData4Example = (signTypedData4 as MethodObject) - .examples?.[0] as ExamplePairingObject; - - // just update address for signTypedData - (signTypedData4Example.params[0] as ExampleObject).value = ACCOUNT_1; - - // update chainId for signTypedData - ( - signTypedData4Example.params[1] as ExampleObject - ).value.domain.chainId = 1337; - - // net_version missing from execution-apis. see here: https://github.com/ethereum/execution-apis/issues/540 - const netVersion: MethodObject = { - name: 'net_version', - summary: 'Returns the current network ID.', - params: [], - result: { - description: 'Returns the current network ID.', - name: 'net_version', - schema: { - type: 'string', - }, - }, - description: 'Returns the current network ID.', - examples: [ - { - name: 'net_version', - description: 'Example of a net_version request', - params: [], - result: { - name: 'net_version', - description: 'The current network ID', - value: '0x1', - }, - }, - ], - }; - // add net_version - (openrpcDocument.methods as MethodObject[]).push( - netVersion as unknown as MethodObject, - ); - - const getEncryptionPublicKey = openrpcDocument.methods.find( - (m) => (m as MethodObject).name === 'eth_getEncryptionPublicKey', - ); - - (getEncryptionPublicKey as MethodObject).examples = [ - { - name: 'getEncryptionPublicKeyExample', - description: 'Example of a getEncryptionPublicKey request', - params: [ - { - name: 'address', - value: ACCOUNT_1, - }, - ], - result: { - name: 'getEncryptionPublicKeyResult', - value: '0x1a8819e0c9bab700', - }, - }, - ]; - - const getTransactionCount = openrpcDocument.methods.find( - (m) => (m as MethodObject).name === 'eth_getTransactionCount', - ); - (getTransactionCount as MethodObject).examples = [ - { - name: 'getTransactionCountExampleEarliest', - description: 'Example of a pending getTransactionCount request', - params: [ - { - name: 'address', - value: ACCOUNT_1, - }, - { - name: 'tag', - value: 'earliest', - }, - ], - result: { - name: 'getTransactionCountResult', - value: '0x0', - }, - }, - { - name: 'getTransactionCountExampleFinalized', - description: 'Example of a pending getTransactionCount request', - params: [ - { - name: 'address', - value: ACCOUNT_1, - }, - { - name: 'tag', - value: 'finalized', - }, - ], - result: { - name: 'getTransactionCountResult', - value: '0x0', - }, - }, - { - name: 'getTransactionCountExampleSafe', - description: 'Example of a pending getTransactionCount request', - params: [ - { - name: 'address', - value: ACCOUNT_1, - }, - { - name: 'tag', - value: 'safe', - }, - ], - result: { - name: 'getTransactionCountResult', - value: '0x0', - }, - }, - { - name: 'getTransactionCountExample', - description: 'Example of a getTransactionCount request', - params: [ - { - name: 'address', - value: ACCOUNT_1, - }, - { - name: 'tag', - value: 'latest', - }, - ], - result: { - name: 'getTransactionCountResult', - value: '0x0', - }, - }, - // returns a number right now. see here: https://github.com/MetaMask/metamask-extension/pull/14822 - // { - // name: 'getTransactionCountExamplePending', - // description: 'Example of a pending getTransactionCount request', - // params: [ - // { - // name: 'address', - // value: ACCOUNT_1, - // }, - // { - // name: 'tag', - // value: 'pending', - // }, - // ], - // result: { - // name: 'getTransactionCountResult', - // value: '0x0', - // }, - // }, - ]; - - const server = mockServer(port, openrpcDocument); + const [doc, filteredMethods, methodsWithConfirmations] = + transformOpenRPCDocument( + MetaMaskOpenRPCDocument as unknown as OpenrpcDocument, + chainId, + ACCOUNT_1, + ); + const parsedDoc = await parseOpenRPCDocument(doc); + + const server = mockServer(port, parsedDoc); server.start(); - // TODO: move these to a "Confirmation" tag in api-specs - const methodsWithConfirmations = [ - 'wallet_requestPermissions', - 'eth_requestAccounts', - 'wallet_watchAsset', - 'personal_sign', // requires permissions for eth_accounts - 'wallet_addEthereumChain', - 'eth_signTypedData_v4', // requires permissions for eth_accounts - 'wallet_switchEthereumChain', - - // commented out because its not returning 4001 error. - // see here https://github.com/MetaMask/metamask-extension/issues/24227 - // 'eth_getEncryptionPublicKey', // requires permissions for eth_accounts - ]; - const filteredMethods = openrpcDocument.methods - .filter((_m: unknown) => { - const m = _m as MethodObject; - return ( - m.name.includes('snap') || - m.name.includes('Snap') || - m.name.toLowerCase().includes('account') || - m.name.includes('crypt') || - m.name.includes('blob') || - m.name.includes('sendTransaction') || - m.name.startsWith('wallet_scanQRCode') || - methodsWithConfirmations.includes(m.name) || - // filters are currently 0 prefixed for odd length on - // extension which doesn't pass spec - // see here: https://github.com/MetaMask/eth-json-rpc-filters/issues/152 - m.name.includes('filter') || - m.name.includes('Filter') - ); - }) - .map((m) => (m as MethodObject).name); - const testCoverageResults = await testCoverage({ - openrpcDocument: (await parseOpenRPCDocument( - openrpcDocument as never, - )) as never, + openrpcDocument: parsedDoc, transport, reporters: [ 'console-streaming', diff --git a/test/e2e/webdriver/chrome.js b/test/e2e/webdriver/chrome.js index fa56c107439e..891828ddefeb 100644 --- a/test/e2e/webdriver/chrome.js +++ b/test/e2e/webdriver/chrome.js @@ -110,6 +110,7 @@ class ChromeDriver { return { driver, extensionUrl: `chrome-extension://${extensionId}`, + extensionId, }; } diff --git a/yarn.lock b/yarn.lock index f068be182e58..69fabf1e73b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2312,13 +2312,12 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/common@npm:3.1.1": - version: 3.1.1 - resolution: "@ethereumjs/common@npm:3.1.1" +"@ethereumjs/common@npm:4.3.0": + version: 4.3.0 + resolution: "@ethereumjs/common@npm:4.3.0" dependencies: - "@ethereumjs/util": "npm:^8.0.5" - crc-32: "npm:^1.2.0" - checksum: 10/dcc3dd9ec23e8817ec0bf5bb2217619a8db08ea937603258831a906702e79c6f6e93b47d6edde551c7f46ce4a0268febacc23cefcb4ca2865be3b5c0bf5ec670 + "@ethereumjs/util": "npm:^9.0.3" + checksum: 10/90f7fe1ba6827b65cd25e9bb4adf07a117ea554a950bb364d5fd9873cb770d383addb0ad34839a91fbec22ebc25516c6fb7e70ae0198c78f933920bf39797a94 languageName: node linkType: hard @@ -2378,6 +2377,18 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/tx@npm:5.3.0": + version: 5.3.0 + resolution: "@ethereumjs/tx@npm:5.3.0" + dependencies: + "@ethereumjs/common": "npm:^4.3.0" + "@ethereumjs/rlp": "npm:^5.0.2" + "@ethereumjs/util": "npm:^9.0.3" + ethereum-cryptography: "npm:^2.1.3" + checksum: 10/4eb48e763d81ea0978648367d61c568c8d10f769c1ea7d32307ebe02299d4fa9fe5d7bf794ec1ee22e92edef6bfe1f459d5816e1c62d3f93602d931807ca488b + languageName: node + linkType: hard + "@ethereumjs/tx@npm:^4.0.2, @ethereumjs/tx@npm:^4.1.1, @ethereumjs/tx@npm:^4.2.0": version: 4.2.0 resolution: "@ethereumjs/tx@npm:4.2.0" @@ -2413,7 +2424,7 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/util@npm:^9.0.2, @ethereumjs/util@npm:^9.1.0": +"@ethereumjs/util@npm:^9.0.2, @ethereumjs/util@npm:^9.0.3, @ethereumjs/util@npm:^9.1.0": version: 9.1.0 resolution: "@ethereumjs/util@npm:9.1.0" dependencies: @@ -4082,6 +4093,17 @@ __metadata: languageName: node linkType: hard +"@json-schema-tools/dereferencer@npm:^1.6.3": + version: 1.6.3 + resolution: "@json-schema-tools/dereferencer@npm:1.6.3" + dependencies: + "@json-schema-tools/reference-resolver": "npm:^1.2.6" + "@json-schema-tools/traverse": "npm:^1.10.4" + fast-safe-stringify: "npm:^2.1.1" + checksum: 10/da6ef5b82a8a9c3a7e62ffcab5c04c581f1e0f8165c0debdb272bb1e08ccd726107ee194487b8fa736cac00fb390b8df74bc1ad1b200eddbe25c98ee0d3d000b + languageName: node + linkType: hard + "@json-schema-tools/meta-schema@npm:1.6.19": version: 1.6.19 resolution: "@json-schema-tools/meta-schema@npm:1.6.19" @@ -4089,7 +4111,7 @@ __metadata: languageName: node linkType: hard -"@json-schema-tools/meta-schema@npm:^1.6.10": +"@json-schema-tools/meta-schema@npm:^1.6.10, @json-schema-tools/meta-schema@npm:^1.7.5": version: 1.7.5 resolution: "@json-schema-tools/meta-schema@npm:1.7.5" checksum: 10/707dc3a285c26c37d00f418e9d0ef8a2ad1c23d4936ad5aab0ce94c9ae36a7a6125c4ca5048513af64b7e6e527b5472a1701d1f709c379acdd7ad12f6409d2cd @@ -4116,10 +4138,10 @@ __metadata: languageName: node linkType: hard -"@json-schema-tools/traverse@npm:^1.7.5, @json-schema-tools/traverse@npm:^1.7.8": - version: 1.10.3 - resolution: "@json-schema-tools/traverse@npm:1.10.3" - checksum: 10/690623740d223ea373d8e561dad5c70bf86461bcedc5fc45da01c87bcdf3284bbdbad3006d4a423f8d82e4b2d4580e45f92c0b272f006024fb597d7f01876215 +"@json-schema-tools/traverse@npm:^1.10.4, @json-schema-tools/traverse@npm:^1.7.5, @json-schema-tools/traverse@npm:^1.7.8": + version: 1.10.4 + resolution: "@json-schema-tools/traverse@npm:1.10.4" + checksum: 10/0027bc90df01c5eeee0833e722b7320b53be8b5ce3f4e0e4a6e45713a38e6f88f21aba31e3dd973093ef75cd21a40c07fe8f112da8f49a7919b1c0e44c904d20 languageName: node linkType: hard @@ -4949,13 +4971,6 @@ __metadata: languageName: node linkType: hard -"@metamask/api-specs@npm:^0.9.3": - version: 0.9.3 - resolution: "@metamask/api-specs@npm:0.9.3" - checksum: 10/803852ba43a0fbabb43aeba2ca63e43d22a99d35710700aa04c92cc85184c93024b052b2ee43831762341848de42d172c99485fa7b659249e75255ff8d29d0b2 - languageName: node - linkType: hard - "@metamask/approval-controller@npm:^7.0.0, @metamask/approval-controller@npm:^7.1.1": version: 7.1.1 resolution: "@metamask/approval-controller@npm:7.1.1" @@ -5836,20 +5851,23 @@ __metadata: languageName: node linkType: hard -"@metamask/multichain@npm:^2.0.0": - version: 2.0.0 - resolution: "@metamask/multichain@npm:2.0.0" +"@metamask/multichain@npm:@metamask-previews/multichain@2.0.0-preview-2a983d27": + version: 2.0.0-preview-2a983d27 + resolution: "@metamask-previews/multichain@npm:2.0.0-preview-2a983d27" dependencies: "@metamask/api-specs": "npm:^0.10.12" "@metamask/controller-utils": "npm:^11.4.4" "@metamask/eth-json-rpc-filters": "npm:^9.0.0" "@metamask/rpc-errors": "npm:^7.0.1" + "@metamask/safe-event-emitter": "npm:^3.0.0" "@metamask/utils": "npm:^10.0.0" + "@open-rpc/schema-utils-js": "npm:^2.0.5" + jsonschema: "npm:^1.4.1" lodash: "npm:^4.17.21" peerDependencies: "@metamask/network-controller": ^22.0.0 "@metamask/permission-controller": ^11.0.0 - checksum: 10/3ae5a1b76070f06b952c1781d36b075a11cc7e94cb3dec35f93e20ed29c5e356cec320079e583e92e3cce52613c125c740bdd020fae0da30a2503b2eb3a2dca0 + checksum: 10/4869ea668a412cd72818562d27f2bdbc1b0bc5d44547e50a7c85c972e3f4f0257a585be7ad2029181d8302f5a4e377b5981bc7c3b11b2382e72ee181f08e52e9 languageName: node linkType: hard @@ -6129,9 +6147,9 @@ __metadata: languageName: node linkType: hard -"@metamask/providers@npm:^18.1.1, @metamask/providers@npm:^18.2.0": - version: 18.2.0 - resolution: "@metamask/providers@npm:18.2.0" +"@metamask/providers@npm:^18.1.1, @metamask/providers@npm:^18.3.0": + version: 18.3.0 + resolution: "@metamask/providers@npm:18.3.0" dependencies: "@metamask/json-rpc-engine": "npm:^10.0.1" "@metamask/json-rpc-middleware-stream": "npm:^8.0.5" @@ -6146,7 +6164,7 @@ __metadata: readable-stream: "npm:^3.6.2" peerDependencies: webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 - checksum: 10/d808f14fc5cf6e240abec61c6b0cb93c0a1d883078ee3d005b9cd49c44e65767a4227d828b19c31500da8e94d85c3632d6957ab35da20b250f6f3a52746ed6b3 + checksum: 10/4a865809747520cc641c88b47d841e8ced776b6a8cc0816929e2855f9e13946060af5554aa696d2949eeb2efd4fd1903e2f45026f3ce4e17906b494e704769ce languageName: node linkType: hard @@ -6984,7 +7002,7 @@ __metadata: languageName: node linkType: hard -"@open-rpc/meta-schema@npm:^1.14.6": +"@open-rpc/meta-schema@npm:^1.14.6, @open-rpc/meta-schema@npm:^1.14.9": version: 1.14.9 resolution: "@open-rpc/meta-schema@npm:1.14.9" checksum: 10/51505dcf7aa1a2285c78953c9b33711cede5f2765aa37dcb9ee7756d689e2ff2a89cfc6039504f0569c52a805fb9aa18f30a7c02ad7a06e793c801e43b419104 @@ -7060,6 +7078,24 @@ __metadata: languageName: node linkType: hard +"@open-rpc/schema-utils-js@npm:^2.0.5": + version: 2.0.5 + resolution: "@open-rpc/schema-utils-js@npm:2.0.5" + dependencies: + "@json-schema-tools/dereferencer": "npm:^1.6.3" + "@json-schema-tools/meta-schema": "npm:^1.7.5" + "@json-schema-tools/reference-resolver": "npm:^1.2.6" + "@open-rpc/meta-schema": "npm:^1.14.9" + ajv: "npm:^6.10.0" + detect-node: "npm:^2.0.4" + fast-safe-stringify: "npm:^2.0.7" + fs-extra: "npm:^10.1.0" + is-url: "npm:^1.2.4" + isomorphic-fetch: "npm:^3.0.0" + checksum: 10/9e10215606e9a00a47b082c9cfd70d05bf0d38de6cf1c147246c545c6997375d94cd3caafe919b71178df58b5facadfd0dcc8b6857bf5e79c40e5e33683dd3d5 + languageName: node + linkType: hard + "@open-rpc/server-js@npm:1.9.3": version: 1.9.3 resolution: "@open-rpc/server-js@npm:1.9.3" @@ -7076,11 +7112,12 @@ __metadata: languageName: node linkType: hard -"@open-rpc/test-coverage@npm:^2.2.2": - version: 2.2.2 - resolution: "@open-rpc/test-coverage@npm:2.2.2" +"@open-rpc/test-coverage@npm:^2.2.4": + version: 2.2.4 + resolution: "@open-rpc/test-coverage@npm:2.2.4" dependencies: "@open-rpc/html-reporter-react": "npm:^0.0.4" + "@open-rpc/meta-schema": "npm:^1.14.6" "@open-rpc/schema-utils-js": "npm:^1.16.2" "@types/isomorphic-fetch": "npm:0.0.35" "@types/lodash": "npm:^4.14.162" @@ -7092,7 +7129,7 @@ __metadata: lodash: "npm:^4.17.20" bin: open-rpc-test-coverage: bin/cli.js - checksum: 10/fc764031d8395dca73187684143f07cd2f6be854bedbd943b086e46f94e5c4207942bf87f1d4ac66f4220f209d6d4a7d50b0eb70d4586e2d07a4e086f0e344b1 + checksum: 10/4bde5b40404a2bdd9f5c2f37b8bdeb1afb21cf0c9a192b508dbf3efd2cf3d2334ed3a149b18bd6546c5754c6f3a78b26832be3677caf2fff9a87f722c7b721f1 languageName: node linkType: hard @@ -8245,6 +8282,13 @@ __metadata: languageName: node linkType: hard +"@sovpro/delimited-stream@npm:^1.1.0": + version: 1.1.0 + resolution: "@sovpro/delimited-stream@npm:1.1.0" + checksum: 10/e78fc97a8509c07b55483df2253137de07b10f14db15d230526a6dd95c86e99d8f54c7af8697806bd16522eec2c50e44e5b4e0294bed80da833a2185f17f3ab6 + languageName: node + linkType: hard + "@spruceid/siwe-parser@npm:2.1.0": version: 2.1.0 resolution: "@spruceid/siwe-parser@npm:2.1.0" @@ -11319,6 +11363,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "@types/uuid@npm:10.0.0" + checksum: 10/e3958f8b0fe551c86c14431f5940c3470127293280830684154b91dc7eb3514aeb79fe3216968833cf79d4d1c67f580f054b5be2cd562bebf4f728913e73e944 + languageName: node + linkType: hard + "@types/uuid@npm:^8.3.0": version: 8.3.0 resolution: "@types/uuid@npm:8.3.0" @@ -11326,7 +11377,7 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:^9.0.0, @types/uuid@npm:^9.0.1, @types/uuid@npm:^9.0.8": +"@types/uuid@npm:^9.0.1, @types/uuid@npm:^9.0.8": version: 9.0.8 resolution: "@types/uuid@npm:9.0.8" checksum: 10/b8c60b7ba8250356b5088302583d1704a4e1a13558d143c549c408bf8920535602ffc12394ede77f8a8083511b023704bc66d1345792714002bfa261b17c5275 @@ -12369,7 +12420,7 @@ __metadata: languageName: node linkType: hard -"aes-js@npm:^3.1.1, aes-js@npm:^3.1.2": +"aes-js@npm:^3.1.2": version: 3.1.2 resolution: "aes-js@npm:3.1.2" checksum: 10/b65916767034a51375a3ac5aad62af452d89a386c1ae7b607bb9145d0bb8b8823bf2f3eba85bdfa52d61c65d5aed90ba90f677b8c826bfa1a8b7ae2fa3b54d91 @@ -13571,6 +13622,13 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^5.0.0": + version: 5.0.0 + resolution: "base-x@npm:5.0.0" + checksum: 10/fa82bc9a963f7a765a3287ba632661669fe553d06ee0d4d4e282640335bff30ec685e3c3b1714e265f697b234facd02a310f1e2465db88f4f1a448e6267fbc65 + languageName: node + linkType: hard + "base32-encode@npm:^1.2.0": version: 1.2.0 resolution: "base32-encode@npm:1.2.0" @@ -13789,10 +13847,10 @@ __metadata: languageName: node linkType: hard -"bitwise@npm:^2.0.4": - version: 2.1.0 - resolution: "bitwise@npm:2.1.0" - checksum: 10/d075220e8b8d1e41d0e60c7081811eef108024a094c4e7f5c2ad67235f3bcac9f6ffd218884900591d602fbd61aff9a6c1d650cd5a0e0e34f12e11623aab5da1 +"bitwise@npm:^2.2.1": + version: 2.2.1 + resolution: "bitwise@npm:2.2.1" + checksum: 10/517aea40f326847935a8ae4367d6beca596982ad55db1d0288a4055c9eba78c6b3ccd10d9ad423df356d946d9a898b36c0d5c06673fba4fb98fb1b58df74788e languageName: node linkType: hard @@ -13920,33 +13978,23 @@ __metadata: languageName: node linkType: hard -"borc@npm:2.1.2": - version: 2.1.2 - resolution: "borc@npm:2.1.2" - dependencies: - bignumber.js: "npm:^9.0.0" - buffer: "npm:^5.5.0" - commander: "npm:^2.15.0" - ieee754: "npm:^1.1.13" - iso-url: "npm:~0.4.7" - json-text-sequence: "npm:~0.1.0" - readable-stream: "npm:^3.6.0" - checksum: 10/a506aec97c3de0a015bf43729a82fe7e7c1ca1f3af72151dacda5d901a673719bfa6e4241d9e09d4b0abdfaf090f5f0645c3397d28e4d4d637f6e3e36e1ed268 - languageName: node - linkType: hard - -"borc@patch:borc@npm%3A2.1.2#./.yarn/patches/borc-npm-2.1.2-8ffcc2dd81.patch::locator=metamask-crx%40workspace%3A.": - version: 2.1.2 - resolution: "borc@patch:borc@npm%3A2.1.2#./.yarn/patches/borc-npm-2.1.2-8ffcc2dd81.patch::version=2.1.2&hash=3e0a96&locator=metamask-crx%40workspace%3A." +"borc@npm:^3.0.0": + version: 3.0.0 + resolution: "borc@npm:3.0.0" dependencies: bignumber.js: "npm:^9.0.0" - buffer: "npm:^5.5.0" + buffer: "npm:^6.0.3" commander: "npm:^2.15.0" ieee754: "npm:^1.1.13" - iso-url: "npm:~0.4.7" - json-text-sequence: "npm:~0.1.0" + iso-url: "npm:^1.1.5" + json-text-sequence: "npm:~0.3.0" readable-stream: "npm:^3.6.0" - checksum: 10/f72b4bb1cef3422a817acbf45201904b36fc00d03613506a3b36d63e6b14713b35970cf1bb8f25721c38d8ac12bbf9ca6098430a1c6b39666d60722641ea8bd3 + bin: + cbor2comment: bin/cbor2comment.js + cbor2diag: bin/cbor2diag.js + cbor2json: bin/cbor2json.js + json2cbor: bin/json2cbor.js + checksum: 10/fc9eaae0a544a300d0eaa4173d523649c9b85ed13f46156d802b5514c75aa4ec80c7ff183afd2bb4067a3166a7561f1a362edeb1673a7760d401b801b688477e languageName: node linkType: hard @@ -14306,6 +14354,15 @@ __metadata: languageName: node linkType: hard +"bs58@npm:^6.0.0": + version: 6.0.0 + resolution: "bs58@npm:6.0.0" + dependencies: + base-x: "npm:^5.0.0" + checksum: 10/7c9bb2b2d93d997a8c652de3510d89772007ac64ee913dc4e16ba7ff47624caad3128dcc7f360763eb6308760c300b3e9fd91b8bcbd489acd1a13278e7949c4e + languageName: node + linkType: hard + "bs58check@npm:2.1.2, bs58check@npm:^2.1.2": version: 2.1.2 resolution: "bs58check@npm:2.1.2" @@ -14327,6 +14384,16 @@ __metadata: languageName: node linkType: hard +"bs58check@npm:^4.0.0": + version: 4.0.0 + resolution: "bs58check@npm:4.0.0" + dependencies: + "@noble/hashes": "npm:^1.2.0" + bs58: "npm:^6.0.0" + checksum: 10/cf5691bdfdf317574f722582360a834f01a36e8f6c850bd5791f04e040b334a0800b7c322ad24c77979c3ed6ef6cf31a6373366b4018223e3005278d491d8799 + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -15923,7 +15990,7 @@ __metadata: languageName: node linkType: hard -"crc-32@npm:^1.2.0": +"crc-32@npm:^1.2.0, crc-32@npm:^1.2.2": version: 1.2.2 resolution: "crc-32@npm:1.2.2" bin: @@ -16830,13 +16897,6 @@ __metadata: languageName: node linkType: hard -"delimit-stream@npm:0.1.0": - version: 0.1.0 - resolution: "delimit-stream@npm:0.1.0" - checksum: 10/9d179cfb91dfbb0702909dfab33bd837fec67c49f0c81495215af578fb08f262d509d76de7431eb11e64e6e71794b9bfe642e372fd33fabbfaf7e060cf5c044f - languageName: node - linkType: hard - "depcheck@npm:^1.4.3": version: 1.4.3 resolution: "depcheck@npm:1.4.3" @@ -17610,9 +17670,24 @@ __metadata: languageName: node linkType: hard +"elliptic@npm:6.5.6": + version: 6.5.6 + resolution: "elliptic@npm:6.5.6" + dependencies: + bn.js: "npm:^4.11.9" + brorand: "npm:^1.1.0" + hash.js: "npm:^1.0.0" + hmac-drbg: "npm:^1.0.1" + inherits: "npm:^2.0.4" + minimalistic-assert: "npm:^1.0.1" + minimalistic-crypto-utils: "npm:^1.0.1" + checksum: 10/09377ec924fdb37775d63e5d7e5ebb2845842e6f08880b68265b1108863e968970c4a4e1c43df622078c8262417deec9a04aeb9d34e8d09a9693e19b5454e1df + languageName: node + linkType: hard + "elliptic@npm:^6.0.0, elliptic@npm:^6.4.0, elliptic@npm:^6.5.4, elliptic@npm:^6.5.7": - version: 6.6.1 - resolution: "elliptic@npm:6.6.1" + version: 6.5.7 + resolution: "elliptic@npm:6.5.7" dependencies: bn.js: "npm:^4.11.9" brorand: "npm:^1.1.0" @@ -17621,7 +17696,7 @@ __metadata: inherits: "npm:^2.0.4" minimalistic-assert: "npm:^1.0.1" minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 10/dc678c9febd89a219c4008ba3a9abb82237be853d9fd171cd602c8fb5ec39927e65c6b5e7a1b2a4ea82ee8e0ded72275e7932bb2da04a5790c2638b818e4e1c5 + checksum: 10/fbad1fad0a5cc07df83f80cc1f7a784247ef59075194d3e340eaeb2f4dd594825ee24c7e9b0cf279c9f1982efe610503bb3139737926428c4821d4fca1bcf348 languageName: node linkType: hard @@ -18804,20 +18879,9 @@ __metadata: linkType: hard "eth-chainlist@npm:~0.0.498": - version: 0.0.498 - resolution: "eth-chainlist@npm:0.0.498" - checksum: 10/a414c0e1f0a877f9ab8bf1cf775556308ddbb66618e368666d4dea9a0b949febedf8ca5440cf57419413404e7661f1e3d040802faf532d0e1618c40ecd334cbf - languageName: node - linkType: hard - -"eth-eip712-util-browser@npm:^0.0.3": - version: 0.0.3 - resolution: "eth-eip712-util-browser@npm:0.0.3" - dependencies: - bn.js: "npm:>4.0.0" - buffer: "npm:^6.0.3" - js-sha3: "npm:^0.8.0" - checksum: 10/f953e553da8326cc7eacffd7edc4c5ca4ba66ddf27546412cbed961900d50bbd8196b44665bd9e8f7d63c3b64df0793a6a8a60cc2f15b340763a78e84c4e7bd4 + version: 0.0.519 + resolution: "eth-chainlist@npm:0.0.519" + checksum: 10/c9767c64e58d140d04e6fcca9589c50edab48a5c57a62f2c749279574a9ab3e13784b05ab4c05c7b020fe8421769bc4119bd7a904df040fbb076827aaac3de23 languageName: node linkType: hard @@ -18901,7 +18965,7 @@ __metadata: languageName: node linkType: hard -"ethereum-cryptography@npm:^2.0.0, ethereum-cryptography@npm:^2.1.2, ethereum-cryptography@npm:^2.2.1": +"ethereum-cryptography@npm:^2.0.0, ethereum-cryptography@npm:^2.1.2, ethereum-cryptography@npm:^2.1.3, ethereum-cryptography@npm:^2.2.1": version: 2.2.1 resolution: "ethereum-cryptography@npm:2.2.1" dependencies: @@ -19544,7 +19608,7 @@ __metadata: languageName: node linkType: hard -"fast-safe-stringify@npm:^2.0.6, fast-safe-stringify@npm:^2.0.7": +"fast-safe-stringify@npm:^2.0.6, fast-safe-stringify@npm:^2.0.7, fast-safe-stringify@npm:^2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" checksum: 10/dc1f063c2c6ac9533aee14d406441f86783a8984b2ca09b19c2fe281f9ff59d315298bc7bc22fd1f83d26fe19ef2f20e2ddb68e96b15040292e555c5ced0c1e4 @@ -21219,29 +21283,29 @@ __metadata: linkType: hard "gridplus-sdk@npm:^2.5.1": - version: 2.5.1 - resolution: "gridplus-sdk@npm:2.5.1" + version: 2.7.1 + resolution: "gridplus-sdk@npm:2.7.1" dependencies: - "@ethereumjs/common": "npm:3.1.1" - "@ethereumjs/tx": "npm:4.1.1" - "@ethersproject/abi": "npm:^5.5.0" - "@types/uuid": "npm:^9.0.0" - aes-js: "npm:^3.1.1" + "@ethereumjs/common": "npm:4.3.0" + "@ethereumjs/rlp": "npm:^5.0.2" + "@ethereumjs/tx": "npm:5.3.0" + "@ethersproject/abi": "npm:^5.7.0" + "@metamask/eth-sig-util": "npm:^7.0.3" + "@types/uuid": "npm:^10.0.0" + aes-js: "npm:^3.1.2" bech32: "npm:^2.0.0" - bignumber.js: "npm:^9.0.1" - bitwise: "npm:^2.0.4" - borc: "npm:^2.1.2" - bs58check: "npm:^2.1.2" - buffer: "npm:^5.6.0" - crc-32: "npm:^1.2.0" - elliptic: "npm:6.5.4" - eth-eip712-util-browser: "npm:^0.0.3" + bignumber.js: "npm:^9.1.2" + bitwise: "npm:^2.2.1" + borc: "npm:^3.0.0" + bs58check: "npm:^4.0.0" + buffer: "npm:^6.0.3" + crc-32: "npm:^1.2.2" + elliptic: "npm:6.5.6" hash.js: "npm:^1.1.7" - js-sha3: "npm:^0.8.0" - rlp: "npm:^3.0.0" - secp256k1: "npm:4.0.2" - uuid: "npm:^9.0.0" - checksum: 10/57deeae78fc5f904309e689054baabaed8b078b896ecfd5d724889c6ea424a113db64c3fd79d4dca7cc5f558167d7af754506df5c0692ee76087822ae60c3873 + js-sha3: "npm:^0.9.3" + secp256k1: "npm:5.0.0" + uuid: "npm:^10.0.0" + checksum: 10/0d81908f69d2972350f4fc6fb721b12f62de643b48dce1d25f4ee2e085899e0cc64605d6cc63590ba870cea72d53f970c05d0fd74979d2c07ad102f3e15b7f82 languageName: node linkType: hard @@ -23541,10 +23605,10 @@ __metadata: languageName: node linkType: hard -"iso-url@npm:~0.4.7": - version: 0.4.7 - resolution: "iso-url@npm:0.4.7" - checksum: 10/355574598d46947f48a63518517bfacf443aae5914991484cdc51c1ebe3f4487d4936ecd0b73a297784d20bf1a4eda3f47975b0fff8022ae20af76b6655e014a +"iso-url@npm:^1.1.5": + version: 1.2.1 + resolution: "iso-url@npm:1.2.1" + checksum: 10/87455fd79166c7b269df7711ea0bee896338330fb46164dd3e6d73ba09c294326ae356b60032dc3217c1455b66f57216a44b95ded8fb2c1c2f9e490396060ef9 languageName: node linkType: hard @@ -24791,12 +24855,12 @@ __metadata: languageName: node linkType: hard -"json-text-sequence@npm:~0.1.0": - version: 0.1.1 - resolution: "json-text-sequence@npm:0.1.1" +"json-text-sequence@npm:~0.3.0": + version: 0.3.0 + resolution: "json-text-sequence@npm:0.3.0" dependencies: - delimit-stream: "npm:0.1.0" - checksum: 10/540973055e03e3caf55e5e06adf88a5d1a4fbefdee44e4c67bbeb614f0d1edd6ea9207f8f9027b6aa86eb6ed4fca3f0dd1f40c4be13f7396efbc0d2f5c5f1e73 + "@sovpro/delimited-stream": "npm:^1.1.0" + checksum: 10/e5dc050aadd626938514363399cf14c409f878628914922c5d470530c3f3473d6b0e16a10338dd7d863aab0291bb0e5e15d71526d14733c22e30cba771b03297 languageName: node linkType: hard @@ -24873,13 +24937,6 @@ __metadata: languageName: node linkType: hard -"jsonschema@npm:1.2.2": - version: 1.2.2 - resolution: "jsonschema@npm:1.2.2" - checksum: 10/aa778e23f1ff879345dabee968c2d7b36d39fe60bb0aa0d251e60d18aed7038499fb203be6c06f4185b0a301b5b187295a46a0a139f19be17b50b6c04b48193d - languageName: node - linkType: hard - "jsonschema@npm:^1.4.1": version: 1.4.1 resolution: "jsonschema@npm:1.4.1" @@ -26635,7 +26692,7 @@ __metadata: "@metamask/accounts-controller": "npm:^20.0.2" "@metamask/address-book-controller": "npm:^6.0.0" "@metamask/announcement-controller": "npm:^7.0.0" - "@metamask/api-specs": "npm:^0.9.3" + "@metamask/api-specs": "npm:^0.10.12" "@metamask/approval-controller": "npm:^7.0.0" "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A45.1.0%23~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch%3A%3Aversion=45.1.0&hash=cfcadc#~/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch" "@metamask/auto-changelog": "npm:^2.1.0" @@ -26681,7 +26738,7 @@ __metadata: "@metamask/message-manager": "npm:^11.0.0" "@metamask/message-signing-snap": "npm:^0.6.0" "@metamask/metamask-eth-abis": "npm:^3.1.1" - "@metamask/multichain": "npm:^2.0.0" + "@metamask/multichain": "npm:@metamask-previews/multichain@2.0.0-preview-2a983d27" "@metamask/name-controller": "npm:^8.0.0" "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch" "@metamask/notification-services-controller": "npm:^0.15.0" @@ -26697,7 +26754,7 @@ __metadata: "@metamask/preferences-controller": "npm:^15.0.1" "@metamask/preinstalled-example-snap": "npm:^0.3.0" "@metamask/profile-sync-controller": "npm:^3.1.1" - "@metamask/providers": "npm:^18.2.0" + "@metamask/providers": "npm:^18.3.0" "@metamask/queued-request-controller": "npm:^7.0.1" "@metamask/rate-limit-controller": "npm:^6.0.0" "@metamask/remote-feature-flag-controller": "npm:^1.1.0" @@ -26723,8 +26780,8 @@ __metadata: "@octokit/core": "npm:^3.6.0" "@open-rpc/meta-schema": "npm:^1.14.6" "@open-rpc/mock-server": "npm:^1.7.5" - "@open-rpc/schema-utils-js": "npm:^1.16.2" - "@open-rpc/test-coverage": "npm:^2.2.2" + "@open-rpc/schema-utils-js": "npm:^2.0.5" + "@open-rpc/test-coverage": "npm:^2.2.4" "@playwright/test": "npm:^1.39.0" "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.5.11" "@popperjs/core": "npm:^2.4.0" @@ -33296,6 +33353,18 @@ __metadata: languageName: node linkType: hard +"secp256k1@npm:5.0.0": + version: 5.0.0 + resolution: "secp256k1@npm:5.0.0" + dependencies: + elliptic: "npm:^6.5.4" + node-addon-api: "npm:^5.0.0" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.2.0" + checksum: 10/6e146c876ef202dbfbb35836d6ccd0ea3779dc09bad632bb9e0fe2e702848a4ee96638f39da54895430de832232d6292d858529e2eda56db3ddda13e40d7facc + languageName: node + linkType: hard + "select-hose@npm:^2.0.0": version: 2.0.0 resolution: "select-hose@npm:2.0.0" @@ -36868,6 +36937,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "uuid@npm:10.0.0" + bin: + uuid: dist/bin/uuid + checksum: 10/35aa60614811a201ff90f8ca5e9ecb7076a75c3821e17f0f5ff72d44e36c2d35fcbc2ceee9c4ac7317f4cc41895da30e74f3885e30313bee48fda6338f250538 + languageName: node + linkType: hard + "uuid@npm:^3.3.3": version: 3.4.0 resolution: "uuid@npm:3.4.0"