npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wonderland/walletless

v1.1.0

Published

Virtual EIP-1193 provider for E2E testing with Wagmi connector

Readme

@wonderland/walletless

image

Lightweight E2E Provider for Web3 DApps - A virtual EIP-1193 provider that enables deterministic, high-performance E2E testing.

Overview

This library provides a man-in-the-middle injected provider. Your DApp keeps using the EIP-1193 interface, while reads go to Anvil and writes are signed locally.

How It Works

graph LR
    DApp["DApp UI"]
    CP1["E2E Provider"]
    CP2["E2E Provider"]
    SIGN["Sign logic / Impersonation"]
    ANVIL["Anvil RPC"]

    DApp -->|Read| CP1
    CP1 -->|eth_call, eth_getBalance, ...| ANVIL

    DApp -->|Write| CP2
    CP2 -->|eth_sendTx, eth_sign, ...| SIGN
    SIGN --> ANVIL

Read operations (eth_call, eth_getBalance, …) → forwarded to Anvil RPC
Write operations (eth_sendTransaction, eth_sign, …) → signed locally, then sent to Anvil
You get production-realistic chains with test-level control.

Advantages

  • Incredible Test Speed: No browser extension overhead, controlled RPC latency, fully virtualized wallet interactions
  • Framework Agnostic: Works with Cypress, Playwright, Selenium, or any E2E testing tool
  • Zero External Dependencies: Built entirely on viem types and native fetch
  • Total Control: Simulate edge cases like RPC errors, specific error codes, delayed signatures, chain switching failures
  • CI/CD Friendly: Runs effortlessly in headless browsers and Docker containers

Benchmarks

Real-world performance comparison using identical test suites on the same Next.js boilerplate:

| Metric | Synpress | Walletless | Speedup | | ------ | -------: | ---------: | ------: | | Real | 34.50s | 2.10s | 16x | | User | 24.24s | 2.98s | 8x | | Sys | 5.34s | 0.75s | 7x |

Test suite:

  1. Connect wallet via RainbowKit modal
  2. Connect + execute 1 ETH transfer

Installation

pnpm add @wonderland/walletless

Usage

Wagmi (plug-and-play)

import { e2eConnector } from "@wonderland/walletless";
import { createConfig, http } from "wagmi";
import { mainnet } from "wagmi/chains";

const isE2E = process.env.CI === "true";

export const config = createConfig({
    chains: [mainnet],
    connectors: isE2E
        ? [e2eConnector()]
        : [
              /* real wallets */
          ],
    transports: {
        [mainnet.id]: isE2E ? http("http://localhost:8545") : http(),
    },
});

Multichain configuration

import { e2eConnector } from "@wonderland/walletless";
import { createConfig, http } from "wagmi";
import { arbitrum, mainnet } from "wagmi/chains";

export const config = createConfig({
    chains: [mainnet, arbitrum],
    connectors: [
        e2eConnector({
            chains: [mainnet, arbitrum],
            rpcUrls: {
                1: "http://localhost:8545",
                42161: "http://localhost:8546",
            },
            account: "0xYourPrivateKey...",
            debug: true,
        }),
    ],
    transports: {
        [mainnet.id]: http("http://localhost:8545"),
        [arbitrum.id]: http("http://localhost:8546"),
    },
});

Standalone provider

import { createE2EProvider } from "@wonderland/walletless";
import { mainnet } from "viem/chains";

const provider = createE2EProvider();

// Use the provider directly
const accounts = await provider.request({ method: "eth_requestAccounts" });
const balance = await provider.request({
    method: "eth_getBalance",
    params: [accounts[0], "latest"],
});

Test control helpers

import {
    ANVIL_ACCOUNTS,
    createE2EProvider,
    disconnect,
    setChain,
    setRejectSignature,
    setRejectTransaction,
    setSigningAccount,
} from "@wonderland/walletless";
// Switch by viem Account object (for custom accounts)
import { privateKeyToAccount } from "viem/accounts";

const provider = createE2EProvider();

// Switch signing account by index (0-9)
setSigningAccount(provider, 0); // First Anvil account
setSigningAccount(provider, 5); // Sixth Anvil account

// Switch by Anvil address (looks up matching private key)
setSigningAccount(provider, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");

// Switch by raw private key
setSigningAccount(provider, "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d");

setSigningAccount(provider, privateKeyToAccount("0x..."));

// Access Anvil accounts directly
console.log(ANVIL_ACCOUNTS[0].address); // 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
console.log(ANVIL_ACCOUNTS[0].privateKey);

// Disconnect (emits disconnect event)
disconnect(provider);

// Reject signatures (throws 4001 "User Rejected Request" error)
setRejectSignature(provider, true);

// Reject transactions (throws 4001 "User Rejected Request" error)
setRejectTransaction(provider, true);

Multichain + chain switching

import { createE2EProvider, setChain } from "@wonderland/walletless";
import { arbitrum, mainnet, optimism } from "viem/chains";

// Create provider with multiple chains and per-chain RPC URLs
const provider = createE2EProvider({
    chains: [mainnet, arbitrum, optimism],
    rpcUrls: {
        1: "http://localhost:8545",
        42161: "http://localhost:8546",
        10: "http://localhost:8547",
    },
});

// Get current chain (first in array is default)
const chainId = await provider.request({ method: "eth_chainId" });
console.log(chainId); // "0x1" (mainnet)

// Switch to Arbitrum - provider now uses arbitrum RPC URL
setChain(provider, arbitrum.id);

// Verify switch
const newChainId = await provider.request({ method: "eth_chainId" });
console.log(newChainId); // "0xa4b1" (Arbitrum)

// Switching to unsupported chain throws an error
try {
    setChain(provider, 137); // Polygon - not in chains array
} catch (e) {
    console.log(e.message); // "Chain 137 is not supported. Supported chains: 1, 42161, 10"
}

Wagmi connector with external provider (test control)

Chain switching validates the target chain, updates the RPC URL, recreates the wallet client, updates state, and emits chainChanged.

When you need to switch accounts or chains during tests while using the wagmi connector, pass your provider to the connector:

import {
    createE2EProvider,
    e2eConnector,
    setChain,
    setSigningAccount,
} from "@wonderland/walletless";
import { createConfig, http } from "wagmi";
import { arbitrum, mainnet } from "wagmi/chains";

// Create provider externally so you can control it
const provider = createE2EProvider({
    chains: [mainnet, arbitrum],
    rpcUrls: {
        1: "http://localhost:8545",
        42161: "http://localhost:8546",
    },
});

export const config = createConfig({
    chains: [mainnet, arbitrum],
    connectors: [e2eConnector({ provider })],
    transports: {
        [mainnet.id]: http("http://localhost:8545"),
        [arbitrum.id]: http("http://localhost:8546"),
    },
});

// In your tests, switch accounts - wagmi will be notified automatically
setSigningAccount(provider, 3); // Switch to 4th Anvil account

// Switch chains during tests (also switches RPC endpoint)
setChain(provider, arbitrum.id); // Switch to Arbitrum

Note: If your wagmi config is created inside a React component (common with RainbowKit or dynamic chain setups), you'll need to use useRef to maintain a stable provider reference. Otherwise, each re-render creates a new provider instance, and calls to setSigningAccount() won't affect the provider that wagmi is actually using.

Test runner example (Playwright)

// tests/swap.spec.ts
import { expect, test } from "@playwright/test";

test("should swap tokens", async ({ page }) => {
    await page.goto("/swap");

    await page.getByTestId("token-input").fill("1.0");
    await page.getByTestId("swap-button").click();

    // Transaction is automatically signed and executed
    await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
});

As a RainbowKit custom Wallet

To instantiate an e2eWallet compatible with rainbowkit, you can do the following :

import { Wallet, WalletDetailsParams } from "@rainbow-me/rainbowkit";
import { e2eConnector } from "@wonderland/walletless";

export const e2eWallet = (): Wallet => ({
    id: "e2e",
    name: "E2E Test Wallet",
    iconUrl:
        'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect fill="%234F46E5" width="100" height="100" rx="20"/><text x="50" y="65" font-size="50" text-anchor="middle" fill="white">E2E</text></svg>',
    iconBackground: "#4F46E5",
    installed: true,
    createConnector: (walletDetails: WalletDetailsParams) => {
        // Create the E2E connector (this returns a CreateConnectorFn)
        const connector = e2eConnector({
            rpcUrls: {
                [sepolia.id]: "http://localhost:8545",
                [mainnet.id]: "http://localhost:8546",
            },
            chains: [sepolia, mainnet],
        });

        // Wrap it in the format expected by RainbowKit
        return createConnector((config) => ({
            ...connector(config),
            ...walletDetails,
        }));
    },
});

It will create a custom rainbowkit wallet using walletless as connector. Then your wagmi config would look like this:

const connectors = connectorsForWallets(
    [
        {
            groupName: "Recommended",
            wallets: isE2E ? [e2eWallet] : [injectedWallet],
        },
    ],
    {
        appName: "Web3 React boilerplate",
        projectId: PROJECT_ID,
    },
);

export const config = createConfig({
    chains: [sepolia, mainnet],
    transports: {
        [sepolia.id]: isE2E ? http("http://localhost:8545") : http(),
        [mainnet.id]: isE2E ? http("http://localhost:8546") : http(),
    },
    connectors,
    // ...rest of your wagmi config...
});

Configuration

E2EConnectorParameters (Wagmi)

The connector accepts either a pre-constructed provider or configuration options:

Option 1: Pass a provider (recommended when you need setSigningAccount())

| Parameter | Type | Description | | ---------- | ------------- | --------------------------------------------- | | provider | E2EProvider | Pre-constructed provider for external control |

Option 2: Let the connector create the provider internally

| Parameter | Type | Default | Description | | --------- | ------------------------ | ------------------------------ | --------------------------------------------------------------------------------- | | chains | Chain[] | [mainnet] | Supported chains (first chain is default) | | rpcUrls | Record<number, string> | {} | Per-chain RPC URLs mapping chainId to URL. Falls back to http://localhost:8545. | | account | Hex \| Account | Anvil's first test private key | Private key or viem Account for signing | | debug | boolean | false | Enable debug logging |

E2EProviderConfig (Standalone)

All parameters are optional with sensible Anvil defaults:

| Parameter | Type | Default | Description | | --------- | ------------------------ | ------------------------------ | --------------------------------------------------------------------------------- | | chains | Chain[] | [mainnet] | Supported chains (first chain is default) | | rpcUrls | Record<number, string> | {} | Per-chain RPC URLs mapping chainId to URL. Falls back to http://localhost:8545. | | account | Hex \| Account | Anvil's first test private key | Private key or viem Account for signing | | debug | boolean | false | Enable debug logging |

setSigningAccount Input Types

| Input Type | Example | Description | | ------------ | ------------------------------------------- | ---------------------------------- | | Index (0-9) | setSigningAccount(provider, 0) | Use Anvil's nth default account | | Address | setSigningAccount(provider, "0x70997...") | Look up matching Anvil account | | Private Key | setSigningAccount(provider, "0x59c69...") | Use any private key (66 chars) | | viem Account | setSigningAccount(provider, viemAccount) | Use a viem Account object directly |

Anvil Reference

This library is designed to work by default with Anvil, a fast local Ethereum development node from Foundry.

Installing Foundry

# Install Foundry (includes anvil, forge, cast, chisel)
curl -L https://foundry.paradigm.xyz | bash

# Run foundryup to install the latest version
foundryup

# Verify installation
anvil --version

Basic Usage

# Start Anvil with defaults (port 8545, 10 accounts, 10000 ETH each, chainId 31337)
anvil

Forking a Chain

# Fork mainnet
anvil --fork-url https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY

# Fork at a specific block
anvil --fork-url https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY --fork-block-number 19000000

# Fork Arbitrum
anvil --fork-url https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY --chain-id 42161

Changing the Port

# Single fork on custom port
anvil --port 8546

# Multiple forks (run in separate terminals)
anvil --port 8545 --fork-url https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY   # Mainnet
anvil --port 8546 --fork-url https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY   # Arbitrum
anvil --port 8547 --fork-url https://opt-mainnet.g.alchemy.com/v2/YOUR_KEY   # Optimism

Chain Configuration

# Set chain ID (useful when not forking)
anvil --chain-id 1337

# Set block time (auto-mining interval in seconds)
anvil --block-time 12

# Disable auto-mining (mine on demand)
anvil --no-mining

Account Configuration

# Generate 20 accounts with 50000 ETH each
anvil --accounts 20 --balance 50000

# Use custom mnemonic
anvil --mnemonic "test test test test test test test test test test test junk"

For the full reference, see the Anvil documentation.

Development

# Install dependencies
pnpm install

# Build
pnpm build

# Run tests
pnpm test

# Lint
pnpm lint

# Format
pnpm format:fix

License

MIT License - see LICENSE for details.

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting a PR.