Skip to content

Commit

Permalink
Add EIP-6963 Provider (#19908)
Browse files Browse the repository at this point in the history
## Explanation

Helps to alleviate `window.ethereum` conflicts by supporting an
alternative way for Dapps to discover and interact with providers. This
PR implements
[EIP-6963](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-6963.md)
which adds an async event based provider discovery standard.

~~NOTE: I'm not sure if we strictly need the changes I made that add
EIP-6963 support to the test dapp, but we probably want to add an e2e
test for EIP-6963 and I'm not sure if we want our tests to rely on an
external site like https://eip6963.org/ . Maybe we can test this by
running some js in page like we do for testing parts of the json rpc in
e2e environment.~~ I've added a spec for this that tests it directly in
the browser console instead

Ticket:
[mmp-869](MetaMask/MetaMask-planning#869)
Related: MetaMask/providers#263
~~Related: MetaMask/test-dapp#243

# Screenshot
<img width="470" alt="Screenshot 2023-09-27 at 4 41 13 PM"
src="https://github.com/MetaMask/metamask-extension/assets/918701/88a0e334-fed9-45e8-80a2-329cb4c94ded">

## Manual Testing Steps

* Install and enable a few other browser extension wallets
* Visit https://eip6963.org/
* MetaMask should show up in the list of providers and play nicely with
all of them

## Pre-merge author checklist

- [x] I've clearly explained:
  - [x] What problem this PR is solving
  - [x] How this problem was solved
  - [x] How reviewers can test my changes
- [x] Sufficient automated test coverage has been added

## Pre-merge reviewer checklist

- [ ] Manual testing (e.g. pull and build branch, run in browser, test
code being changed)
- [ ] PR is linked to the appropriate GitHub issue
- [ ] **IF** this PR fixes a bug in the release milestone, add this PR
to the release milestone

If further QA is required (e.g. new feature, complex testing steps,
large refactor), add the `Extension QA Board` label.

In this case, a QA Engineer approval will be be required.
  • Loading branch information
jiexi authored Oct 11, 2023
1 parent 641ff77 commit 313abb1
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 18 deletions.
7 changes: 7 additions & 0 deletions app/scripts/inpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ cleanContextForImports();

/* eslint-disable import/first */
import log from 'loglevel';
import { v4 as uuid } from 'uuid';
import { WindowPostMessageStream } from '@metamask/post-message-stream';
import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider';
import shouldInjectProvider from '../../shared/modules/provider-injection';
Expand Down Expand Up @@ -59,5 +60,11 @@ if (shouldInjectProvider()) {
connectionStream: metamaskStream,
logger: log,
shouldShimWeb3: true,
providerInfo: {
uuid: uuid(),
name: process.env.METAMASK_BUILD_NAME,
icon: process.env.METAMASK_BUILD_ICON,
rdns: process.env.METAMASK_BUILD_APP_ID,
},
});
}
6 changes: 6 additions & 0 deletions builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,12 @@ env:
# Modified in <root>/development/build/scripts.js:@setEnvironmentVariables
- METAMASK_BUILD_TYPE
# Modified in <root>/development/build/scripts.js:@setEnvironmentVariables
- METAMASK_BUILD_NAME
# Modified in <root>/development/build/scripts.js:@setEnvironmentVariables
- METAMASK_BUILD_APP_ID
# Modified in <root>/development/build/scripts.js:@setEnvironmentVariables
- METAMASK_BUILD_ICON
# Modified in <root>/development/build/scripts.js:@setEnvironmentVariables
- NODE_ENV
# Defined by node itself
# For the purposes of the build system we define it as empty below
Expand Down
21 changes: 9 additions & 12 deletions development/build/manifest.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { promises: fs } = require('fs');
const path = require('path');
const childProcess = require('child_process');
const { mergeWith, cloneDeep, capitalize } = require('lodash');
const { mergeWith, cloneDeep } = require('lodash');

const baseManifest = process.env.ENABLE_MV3
? require('../../app/manifest/v3/_base.json')
Expand All @@ -10,7 +10,7 @@ const { loadBuildTypesConfig } = require('../lib/build-type');

const { TASKS, ENVIRONMENT } = require('./constants');
const { createTask, composeSeries } = require('./task');
const { getEnvironment } = require('./utils');
const { getEnvironment, getBuildName } = require('./utils');

module.exports = createManifestTasks;

Expand Down Expand Up @@ -124,23 +124,20 @@ function createManifestTasks({
return;
}

const mv3Str = process.env.ENABLE_MV3 ? ' MV3' : '';
const lavamoatStr = applyLavaMoat ? ' lavamoat' : '';
const snowStr = shouldIncludeSnow ? ' snow' : '';

// Get the first 8 characters of the git revision id
const gitRevisionStr = childProcess
.execSync('git rev-parse HEAD')
.toString()
.trim()
.substring(0, 8);

const buildName =
buildType === 'mmi'
? `MetaMask Institutional ${mv3Str}`
: `MetaMask ${capitalize(buildType)}${mv3Str}${lavamoatStr}${snowStr}`;

manifest.name = buildName;
manifest.name = getBuildName({
environment,
buildType,
applyLavaMoat,
shouldIncludeSnow,
shouldIncludeMV3: process.env.ENABLE_MV3,
});

manifest.description = `${environment} build from git id: ${gitRevisionStr}`;
}
Expand Down
13 changes: 13 additions & 0 deletions development/build/scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const {
getEnvironment,
logError,
wrapAgainstScuttling,
getBuildName,
getBuildAppId,
getBuildIcon,
} = require('./utils');

const {
Expand Down Expand Up @@ -1181,6 +1184,16 @@ async function setEnvironmentVariables({
testing,
}),
METAMASK_DEBUG: devMode || variables.getMaybe('METAMASK_DEBUG') === true,
METAMASK_BUILD_NAME: getBuildName({
environment,
buildType,
}),
METAMASK_BUILD_APP_ID: getBuildAppId({
buildType,
}),
METAMASK_BUILD_ICON: getBuildIcon({
buildType,
}),
METAMASK_ENVIRONMENT: environment,
METAMASK_VERSION: version,
METAMASK_BUILD_TYPE: buildType,
Expand Down
71 changes: 71 additions & 0 deletions development/build/utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
const path = require('path');
const { readFileSync } = require('fs');
const semver = require('semver');
const { capitalize } = require('lodash');
const { loadBuildTypesConfig } = require('../lib/build-type');
const { BUILD_TARGETS, ENVIRONMENT } = require('./constants');

const BUILD_TYPES_TO_SVG_LOGO_PATH = {
main: './app/images/logo/metamask-fox.svg',
beta: './app/build-types/beta/images/logo/metamask-fox.svg',
flask: './app/build-types/flask/images/logo/metamask-fox.svg',
mmi: './app/build-types/mmi/images/logo/mmi-logo.svg',
desktop: './app/build-types/desktop/images/logo/metamask-fox.svg',
};

/**
* Returns whether the current build is a development build or not.
*
Expand Down Expand Up @@ -218,8 +228,69 @@ function getPathInsideNodeModules(packageName, pathToFiles) {
return targetPath;
}

/**
* Get the name for the current build.
*
* @param {object} options - The build options.
* @param {string} options.buildType - The build type of the current build.
* @param {boolean} options.applyLavaMoat - Flag if lavamoat was applied.
* @param {boolean} options.shouldIncludeSnow - Flag if snow should be included in the build name.
* @param {boolean} options.shouldIncludeMV3 - Flag if mv3 should be included in the build name.
* @param options.environment
* @returns {string} The build name.
*/
function getBuildName({
environment,
buildType,
applyLavaMoat,
shouldIncludeSnow,
shouldIncludeMV3,
}) {
if (environment === ENVIRONMENT.PRODUCTION) {
return 'MetaMask';
}

const mv3Str = shouldIncludeMV3 ? ' MV3' : '';
const lavamoatStr = applyLavaMoat ? ' lavamoat' : '';
const snowStr = shouldIncludeSnow ? ' snow' : '';

return buildType === 'mmi'
? `MetaMask Institutional${mv3Str}`
: `MetaMask ${capitalize(buildType)}${mv3Str}${lavamoatStr}${snowStr}`;
}

/**
* Get the app ID for the current build. Should be valid reverse FQDN.
*
* @param {object} options - The build options.
* @param {string} options.buildType - The build type of the current build.
* @returns {string} The build app ID.
*/
function getBuildAppId({ buildType }) {
const baseDomain = 'io.metamask';
return buildType === 'main' ? baseDomain : `${baseDomain}.${buildType}`;
}

/**
* Get the image data uri for the svg icon for the current build.
*
* @param {object} options - The build options.
* @param {string} options.buildType - The build type of the current build.
* @returns {string} The image data uri for the icon.
*/
function getBuildIcon({ buildType }) {
const svgLogoPath =
BUILD_TYPES_TO_SVG_LOGO_PATH[buildType] ||
BUILD_TYPES_TO_SVG_LOGO_PATH.main;
const svg = readFileSync(svgLogoPath, 'utf8');
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
}

module.exports = {
getBrowserVersionMap,
getBuildName,
getBuildAppId,
getBuildIcon,
getEnvironment,
isDevBuild,
isTestBuild,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@
"@metamask/phishing-controller": "^6.0.0",
"@metamask/post-message-stream": "^6.2.0",
"@metamask/ppom-validator": "^0.5.0",
"@metamask/providers": "^13.0.0",
"@metamask/providers": "^13.1.0",
"@metamask/rate-limit-controller": "^3.0.0",
"@metamask/rpc-methods": "^3.0.0",
"@metamask/safe-event-emitter": "^2.0.0",
Expand Down
94 changes: 94 additions & 0 deletions test/e2e/provider/eip-6963.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
const { strict: assert } = require('assert');
const FixtureBuilder = require('../fixture-builder');
const { convertToHexValue, withFixtures, openDapp } = require('../helpers');

// https://github.com/thenativeweb/uuidv4/blob/bdcf3a3138bef4fb7c51f389a170666f9012c478/lib/uuidv4.ts#L5
const UUID_V4_REGEX =
/(?:^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}$)|(?:^0{8}-0{4}-0{4}-0{4}-0{12}$)/u;

const SVG_DATA_URI_REGEX = /^data:image\/svg\+xml,/u;

describe('EIP-6963 Provider', function () {
const ganacheOptions = {
accounts: [
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: convertToHexValue(25000000000000000000),
},
],
};

it('should respond to the request provider event', async function () {
await withFixtures(
{
dapp: true,
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToTestDapp()
.build(),
ganacheOptions,
title: this.test.title,
},
async ({ driver }) => {
await driver.navigate();
await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER);

await openDapp(driver);
await driver.executeScript(`
window.announceProviderEvents = []
window.addEventListener(
"eip6963:announceProvider",
(event) => {
window.announceProviderEvents.push(event)
}
);
window.dispatchEvent(new Event("eip6963:requestProvider"));
`);
const announceProviderEvents = await driver.executeScript(`
return window.announceProviderEvents.map(event => {
return {
type: event.type,
detail: {
...event.detail,
provider: Boolean(event.detail.provider)
}
}
})
`);

assert.match(announceProviderEvents[0].detail.info.uuid, UUID_V4_REGEX);
delete announceProviderEvents[0].detail.info.uuid;
assert.match(
announceProviderEvents[0].detail.info.icon,
SVG_DATA_URI_REGEX,
);
delete announceProviderEvents[0].detail.info.icon;
assert.deepStrictEqual(announceProviderEvents, [
{
type: 'eip6963:announceProvider',
detail: {
info: {
name: 'MetaMask Main',
rdns: 'io.metamask',
},
provider: true,
},
},
]);

const request = JSON.stringify({
jsonrpc: '2.0',
method: 'eth_chainId',
params: [],
id: 0,
});
const result = await driver.executeScript(`
return window.announceProviderEvents[0].detail.provider.request(${request})
`);

assert.equal(result, '0x539');
},
);
});
});
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4597,9 +4597,9 @@ __metadata:
languageName: node
linkType: hard

"@metamask/providers@npm:^13.0.0":
version: 13.0.0
resolution: "@metamask/providers@npm:13.0.0"
"@metamask/providers@npm:^13.0.0, @metamask/providers@npm:^13.1.0":
version: 13.1.0
resolution: "@metamask/providers@npm:13.1.0"
dependencies:
"@metamask/json-rpc-engine": "npm:^7.1.1"
"@metamask/object-multiplex": "npm:^1.1.0"
Expand All @@ -4612,7 +4612,7 @@ __metadata:
is-stream: "npm:^2.0.0"
json-rpc-middleware-stream: "npm:^4.2.1"
webextension-polyfill: "npm:^0.10.0"
checksum: c6fe1936741a2b782960f0a12148602754fabe55b75707fd80716d5a42ca4f672d838d1ba36873e96f4afdc5360247e3b737c95b7ca532a74e135218cb65033a
checksum: 2ee1802d92d0fd2c733c5d98137e4f92c77a42fbf008a6b19de25d84d48321502031b82aa66501c4ce9c7e863a82c1866fe477349b48ba1151d4b8c2114233e8
languageName: node
linkType: hard

Expand Down Expand Up @@ -23840,7 +23840,7 @@ __metadata:
"@metamask/phishing-warning": "npm:^2.1.0"
"@metamask/post-message-stream": "npm:^6.2.0"
"@metamask/ppom-validator": "npm:^0.5.0"
"@metamask/providers": "npm:^13.0.0"
"@metamask/providers": "npm:^13.1.0"
"@metamask/rate-limit-controller": "npm:^3.0.0"
"@metamask/rpc-methods": "npm:^3.0.0"
"@metamask/safe-event-emitter": "npm:^2.0.0"
Expand Down

0 comments on commit 313abb1

Please sign in to comment.