MetaMask / metamask-extension

:globe_with_meridians: :electric_plug: The MetaMask browser extension enables browsing Ethereum blockchain enabled websites
https://metamask.io
Other
11.65k stars 4.77k forks source link

Recent Breaking Changes: window.web3 Removal and window.ethereum changes #8077

Closed rekmarks closed 3 years ago

rekmarks commented 4 years ago

The Breaking Changes are Live

As of version 9.0.2 of the MetaMask extension (and 1.0.9 of MetaMask Mobile), the breaking changes are live. See the migration guide for more details.

The below information is only useful for historical purposes.


Summary

We are removing window.web3 and making some breaking changes to window.ethereum. These changes apply to all version of MetaMask (extension and mobile).

We committed to maintaining the affected features until November 16, 2020. That day has now come, and the changes may ship at any time. Due to app store review processes, we can't say exactly when, but we will make an announcement here once we know for sure.

New Provider API

We shipped a new, non-breaking Provider (window.ethereum) API in MetaMask 8.0.4 on July 14, 2020, which happened to be MetaMask's 4th birthday 🎊 🦊 🎊. This API is also available through MetaMask mobile.

Please consult our documentation to learn how to use the API.

Upcoming Breaking Changes

These changes will ship on or after November 16, 2020. The version number of MetaMask that includes the breaking changes will be announced by this date.

window.web3 Removal

We are going to remove our injected window.web3. See #7163 for why, and our migration guide for replacement strategies.

See #9156 for the latest build without window.web3, automatically posted by @metamaskbot.

window.ethereum API Changes

We are also going to make some breaking changes to our provider. These include:

What to Do Next

Please see the relevant section of our documentation for details. You can migrate to protect yourself from these changes today. Please see our migration guide for details. There's no reason to wait, migrate today!

imnisen commented 4 years ago

Hi, does the current version 7.7.7 support the new api?

When I try to use ethereum.send('eth_requestAccounts') replace ethereum.enable(), metamask returns {code: -32601, message: "The method undefined does not exist/is not available"}.

rekmarks commented 4 years ago

Hi @imnisen, the new API is not live; no production version of MetaMask supports it. It will say in this issue when it is.

bhayward93 commented 4 years ago

Any recommended ways to test that my site will not be affected by this as a Dapp developer?

rekmarks commented 4 years ago

@bhayward93, if you subscribe to this issue and follow us on Twitter you'll be notified when we have a build with only the new API ready. We'll provide instructions for how to test it, and you'll have a full 6 weeks to do it.

dmvt commented 4 years ago

Hi @imnisen, the new API is not live; no production version of MetaMask supports it. It will say in this issue when it is.

Hey @rekmarks @danfinlay can we get a status update? It looks as though this may be live or partially live. Also, I read in the docs that the plan is to be compliant with EIP 1193, however, EIP 1193 deprecates the send method entirely, preferring request. The docs do not show a request method in the new api. Will it be supported?

Thanks!

Screenshot from 2020-04-05 22-29-33

rekmarks commented 4 years ago

Hi @dmvt, thanks for reaching out.

We are indeed aware of the updates to EIP 1193, because I wrote the PR that deprecated send in favor of a new method, request.

The updates to EIP 1193 followed discussions by the original authors of that EIP, convenience library authors (ethers, web3), dapp developers, and other community members. They were designed to make the new API less breaking for dapps and convenience libraries, and easier to implement for wallets.

Our documentation and the above Medium articles will be updated to reflect the new changes sometime tomorrow, April 6, Pacific Time.

The ship date for the new API is still TBD, and this issue will be updated accordingly.

crazyrabbitLTC commented 4 years ago

Hello all, is there perhaps a timeline we can refer to regarding the new API? Looking to understand when we're going to need to have resources to build out the new support.

hashtag32 commented 4 years ago

I agree with @crazyrabbitLTC, we would appreciate a timeline, since it said early 2020. Now, we are almost in Mid 2020.

bhayward93 commented 4 years ago

Very much in the same boat as @crazyrabbitLTC and @alextreib here. Further details highlighting a predicted timeline would be much appreciated, even if not totally accurate, since we can then roughly allocate resources.

rekmarks commented 4 years ago

We are finally able to give an update about this!

tl;dr

Stop using window.web3 and use window.ethereum or ethers instead. If using window.ethereum, target this API, which will be available soon, in version 8.0.0. If you're already using window.ethereum, you may have to make some minor changes in the next few months.

Action Items

We will ship a non-breaking update that includes the new Provider API in the next few weeks, 8.0.0, and will make an announcement when the time comes. Read on for details.

Changes in Detail

We will ship the new Provider API in the next few weeks, without any breaking changes, and will make an announcement when the time comes.

Background

After our original announcement, there was a movement to make further changes to the standard defining the Provider API, EIP 1193. We were deeply involved in writing those updates, and helped create a version that is non-breaking. This means that, if you rely on ethereum.send or ethereum.sendAsync today, they will continue to work. Nevertheless, we think the new API is pretty sweet, and offers some cool features, and we recommend that you check it out here.

imaksp commented 4 years ago

@rekmarks when this new api will be released? as new dapps also need to use old API currently. so if you can provide timeline, it would be very helpful.

Kharabet commented 4 years ago

@rekmarks I have updated MetaMask extension to v7.7.9 and still get old-signature method ethereum.send(payload, callback) at some pages instead of new ethereum.send(method: string, params: Array<any>) at other pages. What can cause different versions MetaMask APIs injecting?

rekmarks commented 4 years ago

@aksdevac, as early as next week, but probably the week of May 18, 2020.

rekmarks commented 4 years ago

For everyone's knowledge, the issue raised by @Kharabet was due to web3@1.x overwriting ethereum.send. This will not happen with ethereum.request, which will throw an error on overwrite attempts via a Proxy.

samajammin commented 4 years ago

Thanks for the detailed update @rekmarks!

I'm curious - why the decision to "stop reloading the page on network change"? This is something convenience libraries (like ethers) will not be able to easily handle. I agree with Richard Moore's comment here on why MetaMask should continue to reload the page on network change.

adrianmcli commented 4 years ago

@samajammin It's more hassle for the developer but it provides better end-user UX. It allows the user to keep their app state and not lose context. Regarding Ethers.js (it's essentially the same issue w/ Web3.js), you simply acquire a new provider once you get the event.

Here's some recent code I worked on just the other day to do this w/ React hooks:

// re-register MetaMask provider whenever network changes
useEffect(() => {
  window.ethereum.on("chainChanged", (network) => {
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    setProvider(provider)
  });
}, [provider]);
balasan commented 4 years ago

For everyone's knowledge, the issue raised by @Kharabet was due to web3@1.x overwriting ethereum.send. This will not happen with ethereum.request, which will throw an error on overwrite attempts via a Proxy.

@rekmarks is there currently any way to prevent this from happening? Will switching to request be the only way to avoid this going forward?

rekmarks commented 4 years ago

@samajammin, thanks! Regarding reloading on network change, I think @ricmoo makes a convincing argument that it's often a good idea to do so, but we (MetaMask) don't believe that we should make that choice for others.

Regarding changing networks and the average user, it is not true that only dapp devs use test networks, as evidenced by e.g. Reddit's rewards. We're definitely open to the idea of a "dev mode" in MetaMask in the future, but believe that our users are already protected because:

This [not reloading on network change] is something convenience libraries (like ethers) will not be able to easily handle.

With the new Provider API, library and dapp developers can implement this with a single line:

ethereum.on('chainChanged', () => window.location.reload())

I fully support anyone that wants to continue reloading pages on network changes, we're just not going to force that behavior on you by default. We'll continue to recommend ethers regardless!

rekmarks commented 4 years ago

@balasan, AFAIK, there's no way to prevent web3@1.x from overwriting ethereum.send at the moment, other than not using web3@1.x. The version injected by MetaMask β€” which, I'll repeat, everyone should stop using immediately because we are going to stop injecting it later this year β€” does not overwrite ethereum.send.

ethereum.request is absolutely the recommended method going forward. ~Our provider will throw an error if an overwrite of ethereum.request is attempted, and deletions will be ignored.~

hashtag32 commented 4 years ago

Please keep the web3 API alive as long as the ethereum isn't completely defined and has bugs.

rekmarks commented 4 years ago

@alextreib we won’t start the 3-month web3 deprecation period until EIP-1193 has been finalized. At the moment, it looks like this will happen on 2020-06-04. Either way, it will be announced here.

Regardless of 1193, you should stop using our injected version of web3 today. We recommend ethers.

ricmoo commented 4 years ago

Sorry... Late to the game. I'm writing the docs on the best way to restore the behaviour of reloading the page in the "best practices" section of the docs right now. :)

So chiming in with my two cents, feel free to disregard, but this exact thing is leading to other issues, which are terribly difficult to solve (possibly undecidable) in a generic way.

I think the default of reloading still makes sense. It is fine to allow an option to override this behaviour for advanced cases where that makes sense, especially in developer tools (e.g. remix would likely want to handle this).

I would also say that unless the developer is explicitly listening to the "chainChanged" event, the behaviour should not be able to be changed from this default refresh. If a developer is not explicitly listening for the network to change, they are likely not equipped to handle it. If they really know what they are doing, they can register with an empty callback.

I guess I wonder what the value for the default behaviour to not refresh is? As long as there is a way for a developer to announce they are advanced and wish to tackle the nuances, that should be fine. Refreshing ensures any app (ish) works in general across networks, even for inexperience (and more importantly non-veteran) developers. Not refreshing can lead to lost funds, leaking data and inconsistent non-atomic operations.

I'll just give a few examples of small things that become very hard to handle if a dapp developer doesn't fully understand the consequences and minutia of handling a network change (at which time they are responsible to unregister all listeners, purge all caches, and re-register for events, handle in-flight activities).

  1. Listening on an event filter; when the network changes, all callbacks may now need to be de-registered, and re-registered on the new address. As this often happens within a closure, the closure must be re-evaluated. That closure must also have either exposed some way to de-register or itself detect network changes and pull itself out, but manage that it pulls itself out before the new closure registers.
  2. Listening for block events; when the network changes the current block number could either go back far in the past, jump far into the future or stay similar, each of these cases need to be handled possibly differently, deepening on the app.
  3. Listening for Fork events; a once mined transaction now becomes unmined, possibly beyond what would normally be considered safe number of confirmations, which means it may go undetected. The transaction may also have different meaning on this new network, or transactions may still have occurred, but in a different order.

Basically, Turing Complete is hard to deal with. So, the default should be as safe as possible, for the widest audience of developers, and people who want the additional exercise, may do so, but opt-in for it.

That's my protest for now. :)

crazyrabbitLTC commented 4 years ago

I would also like to throw my voice in with regards to refreshing the page. As long as people can switch providers, which often leads to unpredictable outcomes, I also think we should refresh the page. It's so very hard to keep in sync with a network that is changing, non-deterministic, with nodes that might return conflicting data that allowing the user to change networks suddenly without reloading the page just adds to the weight of developers trying to catch edge cases. Sure it's not a nice experience, but if you're changing the network, you're doing something pretty dramatic. It's like connecting to a whole different internet.

So, just another voice.

rekmarks commented 4 years ago

Yeah, we discussed this extensively internally, and are working out a plan for what to with our default behavior. I don't want to make any premature announcements, but unless you have an extremely good reason to handle network changes without refreshing the page, our official recommendation is to do:

ethereum.on('chainChanged', () => window.location.reload())
alcuadrado commented 4 years ago

but unless you have an extremely good reason to handle network changes without refreshing the page, our official recommendation is to do:

ethereum.on('chainChanged', () => window.location.reload())

This makes me think that this API is designed for the least frequent use-case by default. Won't this lead to the majority of apps accidentally breaking? Not talking just of existing ones, but also new ones.

I think giving devs the possibility of not having their pages reloaded on network changes is really important. I'm glad you are adding that. I just worry that it may be yet another hard-to-discover gotcha of the Ethereum platform.

I would also say that unless the developer is explicitly listening to the "chainChanged" event, the behaviour should not be able to be changed from this default refresh.

This idea is really interesting.

rekmarks commented 4 years ago

Alright, here's the complete context:

After discussing it internally, we were convinced by @ricmoo's argument above, that the default behavior should be that the page is reloaded. I also agree with the points raised by @crazyrabbitLTC and @alcuadrado. We also want consumers to be able to override the reload, if they wish.

Now, the problem is that we don't reload by default under most circumstances, at the moment. We only reload by default if our injected window.web3 object has been accessed. (Don't ask why. πŸ˜‘) What we want to do is reload if our window.ethereum object, the provider, has been accessed.

That being the case, enabling reload by default would probably be breaking for existing dapps. Probably not catastrophically breaking, but breaking nonetheless. That being the case, we need to go through a deprecation process. We'll most likely add it to the first round of changes noted previously in this thread, 6 weeks after the launch of MetaMask v8.

Regarding how to override the default, the majority opinion on the team is to have an explicit toggle (e.g. ethereum.stopReloadOnNetworkChange()), rather than an implicit one (e.g. adding a chainChanged listener).

ricmoo commented 4 years ago

Random idea... Not sure if it's good or not, but just thinking out loud. Rather than having functions on the ethereum object, use something like: ethereum.request("com.metamask_stopReloadOnNetworkChange", [])?

I would also like to start (or help start?) an EIP on vendor extensions, like this. I also plan to update EIP-634 to use a similar reverse dot notation, so there is no central registry required, while preserving hierarchal vendor types.

dkent600 commented 4 years ago

I am at a loss to find docs on ethereum.request. Can someone please refer me? Thanks!

dkent600 commented 4 years ago

I am at a loss to find docs on ethereum.request. Can someone please refer me? Thanks!

Found some: https://eips.ethereum.org/EIPS/eip-1193#request

snario commented 4 years ago

(slightly adjacent topic) I just want to throw this in here in case the right person sees this - are there any community adopted TypeScript typings around the ethereum object that exist today and would be expected to be upgraded as this issue and associated EIP develops?

karega commented 3 years ago

What should developers do that were using the web3.js 0.2 version that was being injected? I was using this to sign transactions directly using the PK.

DeltaBalances commented 3 years ago

@rekmarks Maybe this status needs an update.

The New API is: Not Live

Looks like it is partially live. I can currently use ethereum.request({ method: 'eth_requestAccounts' }) on Firefox but not yet on Chrome.

The MetaMask docs show a rollout warning. rollout

rekmarks commented 3 years ago

The new API is live on Chrome and Firefox. Please see the original issue comment for details. We do have upcoming breaking changes, so please make sure to read it.

It's also MetaMask's 4th birthday (🦊), and Bastille Day (πŸ‡«πŸ‡·). Happy birthday to us, and bonne fΓͺte nationale to the French!

rekmarks commented 3 years ago

Reposting latest announcement for great email notification convenience:

The New API is: Live

The new Provider API is live in MetaMask 8.0.4 on Chrome and Firefox as of 2020-07-14, which happens to be MetaMask's 4th birthday πŸŽ‰ .

Please consult our documentation to learn how to use the API.

The new API should be non-breaking for all dapps. If your dapp is broken, it is an all likelihood a bug. Please file a bug report if so.

Upcoming Breaking Changes

We are still going to make some breaking changes to our provider. These include:

We are also still going to remove our injected window.web3.

Please see the relevant section of our documentation for details. You can migrate to protect yourself from these changes today. Please see our migration guide for details. We will announce dates soon, but there's no reason to wait. Migrate today!

rekmarks commented 3 years ago

Since MetaMask v8 launched, we noticed that the ethereum.publicConfigStore object is being used by some consumers. We've also seen it accessed as web3.currentProvider.publicConfigStore.

Despite its name, the publicConfigStore object was never intended for public consumption, and it is scheduled for removal along with the other window.ethereum API changes mentioned in the original issue comment (which has been updated with this removal).

Everything you can do with the publicConfigStore can be done at least as easily via other properties / events / methods / public state etc. If you are using the publicConfigStore, please review why, and replace it with the corresponding APIs in our documentation.

klesun commented 3 years ago

Metamask in our app stopped working today with 8.07. I thought compatibility breaking changes were planned on 30 of August?

Could there be a bug in the 8.07 version of the extension? I'm getting in console:

inpage.js:1 MetaMask: 'ethereum.enable()' is deprecated and may be removed in the future. Please use the 'eth_requestAccounts' RPC method instead.
For more information, see: https://eips.ethereum.org/EIPS/eip-1102

And then just no response in the window.ethereum.enable(), not even a rejection

const ethereumProvider: EthereumProvider = (<any>window).ethereum || null;
const whenAccounts = Promise.resolve()
    .then(() => ethereumProvider.send('eth_requestAccounts'))
    .catch(exc => {
        console.warn('eth_requestAccounts failed, possibly metamask version is < 8.0.7, falling back to legacy .enable()', exc);
        return ethereumProvider.enable();
    });
    const accounts = await addTimeout(30 * 1000, whenAccounts);

image

Also, the proposed ethereum.send('eth_requestAccounts') from eip-1102 does not seem to work either:

TypeError: Cannot create property 'jsonrpc' on string 'eth_requestAccounts'
    at t.exports._rpcRequest (inpage.js:1)
    at t.exports.sendAsync (inpage.js:1)
    at eval (LoginManagerMatic.vue?7c02:178)

(note, judging by https://github.com/MetaMask/metamask-docs/issues/40, there is a mistake in the docs?)

Upd.: yeah, thanks, seems to be specific to our dapp configuration, maybe our global web3 script import

rekmarks commented 3 years ago

@klesun I was unable to reproduce your issue locally using 8.0.7. We have not yet made any breaking changes to the provider API.

I recommend trying ethereum.request({ method: 'eth_requestAccounts' }) instead of ethereum.send or ethereum.enable. Please create a separate issue if the problem persists!

rekmarks commented 3 years ago

@karega, to answer your question about what to do if you're using the injected window.web3, please see our migration guide. I apologize for the delayed response!

klesun commented 3 years ago

Though 8.0.6 did work with our dapp ok today, checked on 4 pcs with and without the update, so I take it that some breaking changes were introduced in 8.0.7. I'll update my comment when I find the cause.

Got the stack trace, here is the issue: https://github.com/MetaMask/metamask-extension/issues/9234

Upd.: this seems to be related to mutation observer/proxy of window.web3 when you assign it to a field in a Vue component due to reactive properties in my case

rekmarks commented 3 years ago

@klesun That's strange, because we didn't ship any changes to the provider in 8.0.7. See: https://github.com/MetaMask/metamask-extension/compare/v8.0.6...v8.0.7

We'd love to understand what the problem is, but please create a new issue instead of updating your previous comment.

rekmarks commented 3 years ago

Breaking Changes Shipping on November 16, 2020

The breaking changes will ship on or shortly after November 16, 2020. Please see the original issue comment for details.

As announced previously, the new API is live, as are tools for moving off our injected window.web3. There's no reason to wait, migrate today!

rekmarks commented 3 years ago

If you want to try out your dapp with a version of MetaMask that does not inject window.web3, you can grab the latest build from #9156 (posted automatically by @metamaskbot) and follow the instructions in our README for loading a custom build of MetaMask.

Please remember to only load custom MetaMask builds from trusted sources, like our GitHub.

dominicletz commented 3 years ago

Regarding eth_chainId the check } else if (chainId !== endpointChainId) { in network-form.component.js seems is using strings and thus is very brittle. Any thought on converting the hex to numbers before the comparison, so that numbers are compared and not formatted strings?

Not sure about other node implementations, but our node implementation is/was returning 0 padded hex strings, and we will have to change that otherwise the comparison will fail, even though the numeric value is the same (Diode prenet chain_id = 15) https://chainid.network/ Corresponding change to stay compatible: https://github.com/diodechain/diode_server_ex/commit/5d4ac2c35c61d5354a3bd97f727a39362522da17

rekmarks commented 3 years ago

@dominicletz I sympathize with your point, but it is brittle by design. We do not want to be in a position where we or consumers of our provider API have to parse chainId values. I'll refer to EIP-695 - which specifies the eth_chainId RPC method - and the Ethereum JSON-RPC API, which specifies data types for Ethereum JSON-RPC methods.

You'll note that, per EIP-695, the chainId value is a QUANTITY, which the Ethereum JSON-RPC API states must not have leading zeros (see the "HEX value encoding" heading in the second link above).

dominicletz commented 3 years ago

@dominicletz I sympathize with your point, but it is brittle by design. We do not want to be in a position where we or consumers of our provider API have to parse chainId values. I'll refer to EIP-695 - which specifies the eth_chainId RPC method - and the Ethereum JSON-RPC API, which specifies data types for Ethereum JSON-RPC methods.

You'll note that, per EIP-695, the chainId value is a QUANTITY, which the Ethereum JSON-RPC API states must not have leading zeros (see the "HEX value encoding" heading in the second link above).

Uh! Thanks for that note, wasn't even aware.

rekmarks commented 3 years ago

You're very welcome, thanks for understanding!

farhanJR commented 3 years ago

After doing all the migration updates mentioned and trying to send the transaction, metamask pops-up but when I click confirm, the website is not able to get the transaction hash from MM.

I used the same code as mentioned on the MM Docs, i.e,

const txHash = await ethereum.request({ method: 'eth_sendTransaction', params: [transactionParameters], });

also, this random error is being poping up on Dev Console:

ERROR Error: Uncaught (in promise): TypeError: this._processRequest(...).catch(...).finally is not a function TypeError: this._processRequest(...).catch(...).finally is not a function at e._handle (inpage.js:15) at e.handle (inpage.js:15) at t.exports._rpcRequest (inpage.js:1) at inpage.js:1 at new ZoneAwarePromise (zone.js:875) at t.exports.request (inpage.js:1) at Web3Service.<anonymous> (web3.service.ts:1452)

at line 1452, on web3service, we have:

window['ethereum'].request({ method: 'eth_sendTransaction', params: [ { to: <Address>, from: <currentWallet>, value: "0x0", data: <encodedData> }, ], }).then((txHash) => console.log(txHash)) .catch((error) => console.error);

Any help/input would be amazing

farhanJR commented 3 years ago

@rekmarks

On this migration, For interacting with Contract on the Angular website we used to do it like this:

this.web3.eth.contract(<abi>).at(<address>) & then use the contract-instance object to interact, but with the MM migration, how do we go about doing the contract interaction??

Also, I updated my implementation to use ethereum.request({method: 'eth_sendTransaction',params: []}), but, when I use it now, it doesn't work and gives me the below error:

e.abi.filter is not a function at c (inpage.js:15)

Also, I have multiple places where contract interaction occurs, any help or guidance would be amazing :)

Just want to understand this contract interaction bit after MM migration.

EDIT: When I do the same things on Ropsten network, all works. MM pops-up and then I can interact and do my transaction. but on Mainnet it shows this error only for a particular function on the contract, rest all other other contract interactions work.

EDIT 2: Solved the issue.

rekmarks commented 3 years ago

It is now November 16, and we will stop injecting window.web3 and ship the breaking changes to our window.ethereum API in an upcoming release.

Due to app store review processes, we can’t say exactly when the changes will go live, but you should no longer rely on any of the affected features, as they may break at any time. Both the MetaMask browser extension and mobile app will be affected.

We will make an announcement here when the changes have started to roll out, along with the version number that includes the changes.

octofinance commented 3 years ago

Thanks for your heads up.