Skip to main content
Skill — Copyable reference implementation. Use as-is or customize. See Skills Philosophy.

Viem Test Client

A copyable implementation of viem’s test client abstraction for local test nodes.

Overview

This Skill provides a complete viem-compatible test client including:
  • createTestClient - Factory returning test client with bound actions
  • mine - Mine blocks
  • setBalance - Set account balance
  • setCode - Set contract bytecode
  • setStorageAt - Set storage slot
  • setNonce - Set account nonce
  • impersonateAccount - Send transactions as any address
  • stopImpersonatingAccount - Stop impersonation
  • snapshot / revert - Save and restore EVM state
  • increaseTime / setNextBlockTimestamp - Time manipulation
  • reset - Reset fork state
  • dumpState / loadState - Serialize and restore state

Quick Start with Anvil

import { createTestClient } from './examples/viem-testclient';

// Create provider (minimal EIP-1193 implementation)
const provider = {
  request: async ({ method, params }) => {
    const response = await fetch('http://localhost:8545', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
    });
    const json = await response.json();
    if (json.error) throw new Error(json.error.message);
    return json.result;
  },
};

// Create test client
const client = createTestClient({
  mode: 'anvil',
  provider,
});

// Fund an account
await client.setBalance({
  address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  value: 10n ** 18n, // 1 ETH
});

// Mine a block
await client.mine({ blocks: 1 });

Snapshot and Revert

Save and restore EVM state:
// Take snapshot before tests
const snapshotId = await client.snapshot();

// Run test operations
await client.setBalance({ address: '0x...', value: 100n ** 18n });
await client.mine({ blocks: 10 });

// Restore original state
await client.revert({ id: snapshotId });
Snapshots are consumed when reverted. Take a new snapshot after reverting if you need to revert again.

Account Impersonation

Send transactions from any address without the private key:
const whale = '0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8'; // Binance

// Impersonate whale account
await client.impersonateAccount({ address: whale });

// Fund the whale for gas (impersonation doesn't give ETH)
await client.setBalance({ address: whale, value: 10n ** 18n });

// Now you can send transactions as the whale
// (using your regular provider for eth_sendTransaction)

// Stop impersonating when done
await client.stopImpersonatingAccount({ address: whale });

Time Manipulation

Control block timestamps for testing time-dependent contracts:
// Advance time by 1 hour
await client.increaseTime({ seconds: 3600 });
await client.mine({ blocks: 1 });

// Set exact timestamp for next block
await client.setNextBlockTimestamp({ timestamp: 1700000000n });
await client.mine({ blocks: 1 });

// Advance multiple blocks with interval
await client.mine({ blocks: 100, interval: 12 }); // 12 seconds between blocks

State Manipulation

Modify accounts directly:
// Set balance
await client.setBalance({
  address: '0x...',
  value: 1000000000000000000n, // 1 ETH in wei
});

// Set nonce
await client.setNonce({
  address: '0x...',
  nonce: 5,
});

// Set contract code
await client.setCode({
  address: '0x...',
  bytecode: '0x608060405260006003...',
});

// Set storage slot
await client.setStorageAt({
  address: '0x...',
  index: 0,
  value: '0x0000000000000000000000000000000000000000000000000000000000000001',
});

Fork Management

Reset and manage forked state:
// Reset to original fork state
await client.reset();

// Reset to specific block
await client.reset({ blockNumber: 18000000n });

// Reset with new fork URL
await client.reset({
  jsonRpcUrl: 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY',
  blockNumber: 18500000n,
});

State Serialization

Save and restore entire state:
// Serialize current state
const state = await client.dumpState();

// Store state somewhere (file, database, etc.)
localStorage.setItem('testState', state);

// Later, restore state
await client.loadState({ state });

Automine Control

Control automatic block mining:
// Disable automine
await client.setAutomine(false);

// Send multiple transactions...
// They accumulate in mempool

// Mine them all at once
await client.mine({ blocks: 1 });

// Re-enable automine
await client.setAutomine(true);

Mode Differences

Different test nodes use different RPC methods:
ActionAnvilHardhatGanache
mineanvil_minehardhat_mineevm_mine
setBalanceanvil_setBalancehardhat_setBalanceevm_setAccountBalance
setCodeanvil_setCodehardhat_setCodeevm_setAccountCode
impersonateanvil_impersonateAccounthardhat_impersonateAccountN/A
automine onevm_setAutomine(true)evm_setAutomine(true)miner_start
automine offevm_setAutomine(false)evm_setAutomine(false)miner_stop
The test client handles these differences automatically based on the mode parameter.

Hardhat Example

const client = createTestClient({
  mode: 'hardhat',
  provider,
});

// Same API, different RPC methods under the hood
await client.mine({ blocks: 1 }); // Uses hardhat_mine

Ganache Example

const client = createTestClient({
  mode: 'ganache',
  provider,
});

// Ganache has some limitations
await client.mine({ blocks: 1 }); // Uses evm_mine with different params
await client.setBalance({ address: '0x...', value: 100n }); // Uses evm_setAccountBalance
Ganache does not support impersonateAccount, setNonce, or some other test actions. Use Anvil or Hardhat for full functionality.

Files

Copy these files into your codebase:
  • createTestClient.js - Main factory function
  • mine.js - Mine blocks
  • setBalance.js - Set account balance
  • setCode.js - Set contract code
  • setStorageAt.js - Set storage slot
  • setNonce.js - Set account nonce
  • impersonateAccount.js - Impersonate address
  • stopImpersonatingAccount.js - Stop impersonating
  • snapshot.js - Create EVM snapshot
  • revert.js - Revert to snapshot
  • increaseTime.js - Advance time
  • setNextBlockTimestamp.js - Set next block timestamp
  • dropTransaction.js - Remove tx from mempool
  • reset.js - Reset fork
  • dumpState.js - Serialize state
  • loadState.js - Load state
  • setAutomine.js - Control automine
  • TestClientTypes.ts - TypeScript type definitions
  • errors.ts - Custom error classes
  • index.ts - Module exports

Differences from Viem

This implementation:
  1. Uses Voltaire’s Address and Hex primitives
  2. Requires explicit provider (no transport abstraction)
  3. Does not include chain configuration
  4. Simplified error handling
For production use, consider adding:
  • Transport abstraction (HTTP, WebSocket, IPC)
  • Chain configuration with native currency
  • Request batching
  • Retry logic for RPC calls