Web3.js extending the window interface type definitions
Asked Answered
A

4

10

Web3.js web3 into the window object.

Browser Wallets like MetaMask inject ethereum into the window object.

In typescript right now to mitigate compile errors I'm casting as follows (window as any).ethereum

After looking in the Web3 repository and Wallet repos (such as MetaMask) there are no importable / copyable typescript definitions / interfaces for the Window object.

A possible solution is to write my own interface and extend the Window, look at the Window object and try to infer the types - not ideal

Other developers that have used web3.js and typescript, how did you get past the Window type interface issues and intellisense suggestions in VS Code?

Ashok answered 30/12, 2020 at 9:48 Comment(6)
Technically, window.ethereum is injected by MetaMask or any other in-browser wallet. Note that other wallets do not have window.ethereum. ethereum.stackexchange.com/questions/82531/…Pampas
Thanks @MikkoOhtamaa i'm assuming that the most popular and adopted wallets are the one's that will inject it into the browser.Ashok
You are assuming incorrect. Only browser plugin wallets can inject ethereum and most wallets are mobile wallets.Pampas
@MikkoOhtamaa i've updated the question to reflect your explanation, thanks for clarifying.Ashok
And your question is still very valid :) But it might be a better ask a generic question "How toa add new window variables in TypeScript". Because that is what you want to do, there is nothing Ethereum specific in your question.Pampas
@MikkoOhtamaa Sorry, but the question I have isn't around extending the interface of the Window that is relatively simple to do as documented here: #12709574 I'm more interested in how other developers have handled the issue of Web3.js's and MetaMasks's lack of typings for extending the window object. I was digging around the Web3.js repository and found some typings that include the functions available on window.ethereum so that might be a good place to start.Ashok
P
14

I came across this just recently as well. I could not find an appropriate typings package from DefinitelyTyped so I started extrapolating from my own usage and the Metamask Documentation and created something that works so far.

Perhaps the community could edit this answer with their own contributions.

In order to use the ethereum object without TS complaints, I declare it in the window object:

declare global {
    interface Window {
        ethereum: Ethereumish;
    }
}

The makeshift Ethereum Provider types, Ethereumish looks like this:

import { ProviderMessage, ProviderRpcError, ProviderConnectInfo, RequestArguments } from 'hardhat/types';

export interface EthereumEvent {
    connect: ProviderConnectInfo;
    disconnect: ProviderRpcError;
    accountsChanged: Array<string>;
    chainChanged: string;
    message: ProviderMessage
}

type EventKeys = keyof EthereumEvent;
type EventHandler<K extends EventKeys> = (event: EthereumEvent[K]) => void;

export interface Ethereumish {
    autoRefreshOnNetworkChange: boolean;
    chainId: string;
    isMetaMask?: boolean;
    isStatus?: boolean;
    networkVersion: string;
    selectedAddress: any;

    on<K extends EventKeys>(event: K, eventHandler: EventHandler<K>): void;
    enable(): Promise<any>;
    request?: (request: { method: string, params?: Array<any> }) => Promise<any>
    /**
     * @deprecated
     */
    send?: (request: { method: string, params?: Array<any> }, callback: (error: any, response: any) => void) => void
    sendAsync: (request: RequestArguments) => Promise<unknown>
}

As you can see, I have not been able to figure out the exact types of many things so far, but the important methods, send and sendAsync are accurate in my experience.

Another useful template is something I found inside @ethersproject/providers/src.ts/web3-provider.ts

export type ExternalProvider = {
    isMetaMask?: boolean;
    isStatus?: boolean;
    host?: string;
    path?: string;
    sendAsync?: (request: { method: string, params?: Array<any> }, callback: (error: any, response: any) => void) => void
    send?: (request: { method: string, params?: Array<any> }, callback: (error: any, response: any) => void) => void
    request?: (request: { method: string, params?: Array<any> }) => Promise<any>
}

this can be used when loading a new provider

new ethers.providers.Web3Provider(myProvider: ExternalProvider)
Phenetidine answered 23/5, 2021 at 23:25 Comment(2)
This repo from metamask seems to provide the type definition MetaMaskInpageProvider: github.com/MetaMask/providersGrandam
it seems very heavy to import hardhat into client dev just for the type definitions.Bays
P
30

The official Metamask Provider repo now exports types you could/should use for extending the Window interface.

Same as in @Felipe's answer with MetamaskInpageProvider instead of Ethereumish, add the following to a Typescript declaration file in your project.

// metamask.d.ts
import { MetaMaskInpageProvider } from "@metamask/providers";

declare global {
  interface Window {
    ethereum: MetaMaskInpageProvider;
  }
}

Plata answered 23/11, 2021 at 1:11 Comment(3)
I had to use ethereum?: MetaMaskInpageProvider; for this to work for me. Note the ?Bipartite
Could you provide the filename and location this should be placed in? This would prove useful for the less experienced.Mccreery
Made it clear it should go in a Typescript declaration file @MccreeryPlata
P
14

I came across this just recently as well. I could not find an appropriate typings package from DefinitelyTyped so I started extrapolating from my own usage and the Metamask Documentation and created something that works so far.

Perhaps the community could edit this answer with their own contributions.

In order to use the ethereum object without TS complaints, I declare it in the window object:

declare global {
    interface Window {
        ethereum: Ethereumish;
    }
}

The makeshift Ethereum Provider types, Ethereumish looks like this:

import { ProviderMessage, ProviderRpcError, ProviderConnectInfo, RequestArguments } from 'hardhat/types';

export interface EthereumEvent {
    connect: ProviderConnectInfo;
    disconnect: ProviderRpcError;
    accountsChanged: Array<string>;
    chainChanged: string;
    message: ProviderMessage
}

type EventKeys = keyof EthereumEvent;
type EventHandler<K extends EventKeys> = (event: EthereumEvent[K]) => void;

export interface Ethereumish {
    autoRefreshOnNetworkChange: boolean;
    chainId: string;
    isMetaMask?: boolean;
    isStatus?: boolean;
    networkVersion: string;
    selectedAddress: any;

    on<K extends EventKeys>(event: K, eventHandler: EventHandler<K>): void;
    enable(): Promise<any>;
    request?: (request: { method: string, params?: Array<any> }) => Promise<any>
    /**
     * @deprecated
     */
    send?: (request: { method: string, params?: Array<any> }, callback: (error: any, response: any) => void) => void
    sendAsync: (request: RequestArguments) => Promise<unknown>
}

As you can see, I have not been able to figure out the exact types of many things so far, but the important methods, send and sendAsync are accurate in my experience.

Another useful template is something I found inside @ethersproject/providers/src.ts/web3-provider.ts

export type ExternalProvider = {
    isMetaMask?: boolean;
    isStatus?: boolean;
    host?: string;
    path?: string;
    sendAsync?: (request: { method: string, params?: Array<any> }, callback: (error: any, response: any) => void) => void
    send?: (request: { method: string, params?: Array<any> }, callback: (error: any, response: any) => void) => void
    request?: (request: { method: string, params?: Array<any> }) => Promise<any>
}

this can be used when loading a new provider

new ethers.providers.Web3Provider(myProvider: ExternalProvider)
Phenetidine answered 23/5, 2021 at 23:25 Comment(2)
This repo from metamask seems to provide the type definition MetaMaskInpageProvider: github.com/MetaMask/providersGrandam
it seems very heavy to import hardhat into client dev just for the type definitions.Bays
S
2

My friends, if you are using React with TypeScript just do this and everything goes right! Trust me :)

  1. create global.d.ts in the root of your project and put this code

     declare global {
         interface Window {
             ethereum: import('ethers').providers.ExternalProvider;
         }
     }
     
  2. Then in react-app-env.d.ts put this code

     interface Window {
         ethereum: any;
     }
     

Now you have the code snippet and no error!!

Sergent answered 7/2, 2023 at 18:19 Comment(0)
R
1

I will just dump what I use currently here.

src/{anything}.ts e.g. src/interfaces.d.ts
interface Window {
  // pick one
  ethereum: EthereumProvider
  // ethereum: ExternalProvider
  // ethereum: AbstractProvider
}

// ExternalProvider seems to be the official ethersproject type for the window.ethereum object, however, `new Web3(ethereum)` does not like it so we must improvise.
declare type ExternalProvider = import('@ethersproject/providers').ExternalProvider
declare type AbstractProvider = import('web3/node_modules/web3-core/types').AbstractProvider
interface EthereumProvider extends ExternalProvider {
  _state: {
    accounts: string[]
  }
  on(event: 'close' | 'accountsChanged' | 'chainChanged' | 'networkChanged', callback: (payload: any) => void): void
  once(event: 'close' | 'accountsChanged' | 'chainChanged' | 'networkChanged', callback: (payload: any) => void): void
  removeAllListeners(): void
  sendAsync: AbstractProvider['sendAsync']
}

This file is a script as it does not import and export anything (unlike module), making its declarations ambient - available globally. Just make sure it is included in the tsconfig.json file in the include/blob array "include": ["..."].

More reading:
https://github.com/MetaMask/providers
github.com~ethers.js @ ExternalProvider

Rosena answered 19/5, 2022 at 0:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.