// Copyright 2019-2025 @pezkuwi/extension-compat-metamask authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { Injected, InjectedAccount, InjectedWindow } from '@pezkuwi/extension-inject/types'; import type { SignerPayloadRaw, SignerResult } from '@pezkuwi/types/types'; import type { HexString } from '@pezkuwi/util/types'; import detectEthereumProvider from '@metamask/detect-provider'; import Web3 from 'web3'; import { assert } from '@pezkuwi/util'; export { packageInfo } from './packageInfo.js'; interface RequestArguments { method: string; params?: unknown[]; } interface EthRpcSubscription { unsubscribe: () => void } interface EthereumProvider { request: (args: RequestArguments) => Promise; isMetaMask: boolean; on: (name: string, cb: (value: unknown) => void) => EthRpcSubscription; } interface Web3Window extends InjectedWindow { // this is injected by metaMask ethereum: unknown; } function isMetaMaskProvider (prov: unknown): EthereumProvider { assert(prov && (prov as EthereumProvider).isMetaMask, 'Injected provider is not MetaMask'); return (prov as EthereumProvider); } // transform the Web3 accounts into a simple address/name array function transformAccounts (accounts: string[]): InjectedAccount[] { return accounts.map((address, i) => ({ address, name: `MetaMask Address #${i}`, type: 'ethereum' })); } // add a compat interface of metaMaskSource to window.injectedWeb3 function injectMetaMaskWeb3 (win: Web3Window): void { // decorate the compat interface win.injectedWeb3['Web3Source'] = { enable: async (): Promise => { const providerRaw = await detectEthereumProvider({ mustBeMetaMask: true }); const provider = isMetaMaskProvider(providerRaw); await provider.request({ method: 'eth_requestAccounts' }); return { accounts: { get: async (): Promise => { const response = (await provider.request({ method: 'eth_requestAccounts' })) as string[]; return transformAccounts(response); }, subscribe: (cb: (accounts: InjectedAccount[]) => void): (() => void) => { const sub = provider.on('accountsChanged', (accounts): void => { cb(transformAccounts(accounts as string[])); }); // TODO: add onchainchanged return (): void => { sub.unsubscribe(); }; } }, signer: { signRaw: async (raw: SignerPayloadRaw): Promise => { const signature = (await provider.request({ method: 'eth_sign', params: [raw.address, Web3.utils.sha3(raw.data)] })) as HexString; return { id: 0, signature }; } } }; }, version: '0' // TODO: win.ethereum.version }; } export default function initMetaMask (): Promise { return new Promise((resolve): void => { const win = window as Window & Web3Window; if (win.ethereum) { injectMetaMaskWeb3(win); resolve(true); } else { resolve(false); } }); }