UNPKG

@finn_gal/patchright-wallet-mock-ts

Version:

Mock Web3 Browser wallets, like Metamask, in Patchright tests.

130 lines (115 loc) 4.59 kB
import { createWallet } from './createWallet'; import cors from 'cors'; import { randomUUID } from 'crypto'; import express from 'express'; import net from 'net'; import type { Page } from 'patchright'; import { Chain, LocalAccount, Transport } from 'viem'; import { Wallet } from './createWallet'; declare global { interface Window { ethereum?: EIP1193Provider; eip1193Request?: (params: eip1193RequestParams) => Promise<any>; } } let wallets: Map<string, Wallet> = new Map(); export async function installMockWallet({ debug, ...params }: & { debug?: boolean; } & { page: Page } & (| { account: LocalAccount; transports?: Record<number, Transport>; defaultChain?: Chain; } | { wallet: Wallet; })) { const freePort = await new Promise(res => { const srv = net.createServer(); srv.listen(0, () => { const port = (srv.address() as any).port; srv.close((err) => res(port)); }); }); const app = express(); app.use(cors({ origin: '*', methods: [ 'POST' ], allowedHeaders: [ 'Content-Type' ] })); app.use(express.json()); app.post('/eip1193', async (req: any, res: any) => { const { method, params, uuid, debug } = req.body; const wallet = wallets.get(uuid); if ( !wallet ) return res.status(400).json({ error: 'Wallet not found' }); try { const result = await wallet.request({ method, params }); if ( debug ) console.log('WALLET', uuid.substring(0, 8), 'REQUEST', method, params, 'RESULT', result); res.json(result); } catch ( error: any ) { if ( debug ) console.log('WALLET', uuid.substring(0, 8), 'REQUEST', method, params, 'ERROR', error); res.status(500).json({ error: error.message }); } }); app.listen(freePort); const browserOrPage = params.page; const wallet = 'wallet' in params ? params.wallet : createWallet(params.account, params.transports, params.defaultChain); const uuid = randomUUID(); wallets.set(uuid, wallet); await browserOrPage.addInitScript(({ uuid, debug, freePort }) => { window.eip1193Request = async ({ method, params }) => { try { const response = await fetch(`http://localhost:${freePort}/eip1193`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ method, params, uuid, debug }) }); const result = await response.json(); if ( !response.ok ) throw new Error(result.error || 'Request failed'); return result; } catch ( e ) { if ( debug ) console.error('EIP-1193 Error:', e); throw e; } }; function announceMockWallet() { const provider = { request: async (request: any) => { return await window.eip1193Request!({ ...request, uuid, debug }); }, on: () => {}, removeListener: () => {} }; const info = { uuid, name: 'MetaMask', icon: '', rdns: 'io.metamask' }; const detail = { info, provider }; const announceEvent = new CustomEvent('eip6963:announceProvider', { detail: Object.freeze(detail) }); window.dispatchEvent(announceEvent); window.ethereum = provider; } announceMockWallet(); window.addEventListener('eip6963:requestProvider', () => announceMockWallet()); window.addEventListener('DOMContentLoaded', () => announceMockWallet()); }, { uuid, debug, freePort } ); } interface EIP6963ProviderInfo { uuid: string; name: string; icon: string; rdns: string; } interface EIP1193Provider { request: (request: { method: string; params?: Array<unknown>; }) => Promise<unknown>; // Standard method for sending requests per EIP-1193 on: () => void; removeListener: () => void; } export interface EIP6963ProviderDetail { info: EIP6963ProviderInfo; provider: EIP1193Provider; } export interface eip1193RequestParams { method: string; params?: Array<unknown>; uuid: string; debug?: boolean; }