/*global Web3*/ // TODO:deprecate:2020 // Delete this file import web3Entitites from './web3-entities.json' import 'web3/dist/web3.min' const shouldLogUsage = ![ 'docs.metamask.io', 'metamask.github.io', 'metamask.io', ].includes(window.location.hostname) /** * To understand how we arrived at this implementation, please see: * https://github.com/ethereum/web3.js/blob/0.20.7/DOCUMENTATION.md */ export default function setupWeb3(log) { // export web3 as a global, checking for usage let reloadInProgress = false let lastTimeUsed let lastSeenNetwork let hasBeenWarned = false const web3 = new Web3(window.ethereum) web3.setProvider = function () { log.debug('MetaMask - overrode web3.setProvider') } log.debug('MetaMask - injected web3') Object.defineProperty(window.ethereum, '_web3Ref', { enumerable: false, writable: true, configurable: true, value: web3.eth, }) // Setup logging of nested property usage if (shouldLogUsage) { // web3 namespaces with common and uncommon dapp actions const includedTopKeys = [ 'eth', 'db', 'shh', 'net', 'personal', 'bzz', 'version', ] // For each top-level property, create appropriate Proxy traps for all of // their properties includedTopKeys.forEach((topKey) => { const applyTrapKeys = new Map() const getTrapKeys = new Map() Object.keys(web3[topKey]).forEach((key) => { const path = `web3.${topKey}.${key}` if (web3Entitites[path]) { if (web3Entitites[path] === 'function') { applyTrapKeys.set(key, path) } else { getTrapKeys.set(key, path) } } }) // Create apply traps for function properties for (const [key, path] of applyTrapKeys) { web3[topKey][key] = new Proxy(web3[topKey][key], { apply: (...params) => { window.ethereum.request({ method: 'metamask_logInjectedWeb3Usage', params: [ { action: 'apply', path, }, ], }) // Call function normally return Reflect.apply(...params) }, }) } // Create get trap for non-function properties web3[topKey] = new Proxy(web3[topKey], { get: (web3Prop, key, ...params) => { const name = stringifyKey(key) if (getTrapKeys.has(name)) { window.ethereum.request({ method: 'metamask_logInjectedWeb3Usage', params: [ { action: 'get', path: getTrapKeys.get(name), }, ], }) } // return value normally return Reflect.get(web3Prop, key, ...params) }, }) }) } const topLevelFunctions = [ 'isConnected', 'setProvider', 'reset', 'sha3', 'toHex', 'toAscii', 'fromAscii', 'toDecimal', 'fromDecimal', 'fromWei', 'toWei', 'toBigNumber', 'isAddress', ] // apply-trap top-level functions topLevelFunctions.forEach((key) => { // This type check is probably redundant, but we've been burned before. if (typeof web3[key] === 'function') { web3[key] = new Proxy(web3[key], { apply: (...params) => { window.ethereum.request({ method: 'metamask_logInjectedWeb3Usage', params: [ { action: 'apply', path: `web3.${key}`, }, ], }) // Call function normally return Reflect.apply(...params) }, }) } }) const web3Proxy = new Proxy(web3, { get: (...params) => { // get the time of use lastTimeUsed = Date.now() // show warning once on web3 access if (!hasBeenWarned) { console.warn( `MetaMask: We will stop injecting web3 in Q4 2020.\nPlease see this article for more information: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`, ) hasBeenWarned = true } // return value normally return Reflect.get(...params) }, }) Object.defineProperty(global, 'web3', { enumerable: false, writable: true, configurable: true, value: web3Proxy, }) window.ethereum._publicConfigStore.subscribe((state) => { // if the auto refresh on network change is false do not // do anything if (!window.ethereum.autoRefreshOnNetworkChange) { return } // if reload in progress, no need to check reload logic if (reloadInProgress) { return } const currentNetwork = state.networkVersion // set the initial network if (!lastSeenNetwork) { lastSeenNetwork = currentNetwork return } // skip reload logic if web3 not used if (!lastTimeUsed) { return } // if network did not change, exit if (currentNetwork === lastSeenNetwork) { return } // initiate page reload reloadInProgress = true const timeSinceUse = Date.now() - lastTimeUsed // if web3 was recently used then delay the reloading of the page if (timeSinceUse > 500) { triggerReset() } else { setTimeout(triggerReset, 500) } }) } // reload the page function triggerReset() { global.location.reload() } /** * Returns a "stringified" key. Keys that are already strings are returned * unchanged, and any non-string values are returned as "typeof ". * * @param {any} key - The key to stringify */ function stringifyKey(key) { return typeof key === 'string' ? key : `typeof ${typeof key}` }