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:
| Action | Anvil | Hardhat | Ganache |
|---|
| mine | anvil_mine | hardhat_mine | evm_mine |
| setBalance | anvil_setBalance | hardhat_setBalance | evm_setAccountBalance |
| setCode | anvil_setCode | hardhat_setCode | evm_setAccountCode |
| impersonate | anvil_impersonateAccount | hardhat_impersonateAccount | N/A |
| automine on | evm_setAutomine(true) | evm_setAutomine(true) | miner_start |
| automine off | evm_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:
- Uses Voltaire’s
Address and Hex primitives
- Requires explicit provider (no transport abstraction)
- Does not include chain configuration
- 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