Local Transaction Signing
Relevant source files
The following files were used as context for generating this wiki page:
Local Transaction Signing is a core mechanism in the @wonderland/walletless library that enables a "Man-in-the-Middle" (MitM) architecture for E2E testing of Web3 decentralized applications (DApps). By intercepting EIP-1193 requests, the system distinguishes between read-only blockchain queries and state-changing write operations. While reads are forwarded to an external RPC node (typically Anvil), write operations and signature requests are captured and processed locally using private keys managed within the provider.
This architecture eliminates the need for manual interaction with browser extensions like MetaMask during automated testing. It provides a programmatic way to sign transactions, messages, and typed data, allowing for deterministic and high-performance testing environments. Sources: README.md:10-23, src/provider.ts:88-95
Architecture and Data Flow
The local signing system operates by routing different categories of JSON-RPC methods through specialized handlers. When a DApp issues a request via the provider's request method, the system identifies the method type (Read, Wallet, or Write) and executes the corresponding logic.
Request Routing Logic
The following diagram illustrates how the request function routes incoming calls:
Supported Write Methods
The system identifies "Write" methods using a predefined list. These methods are intercepted to perform local signing using a viem WalletClient.
| Method | Description |
|---|---|
eth_sendTransaction | Signs and broadcasts a transaction |
eth_sign | Signs a message (Legacy) |
personal_sign | Signs a message with a prefix |
eth_signTypedData_v4 | Signs EIP-712 typed data |
eth_sendRawTransaction | Forwards pre-signed transactions directly to RPC |
Sources: src/constants.ts:161-171, src/provider.ts:245-316
Local Signing Implementation
The handleWriteMethod function manages the lifecycle of a signing request. It utilizes a viem Account (derived from a private key) and a WalletClient to generate cryptographic signatures.
Transaction Processing Flow
When eth_sendTransaction is called, the provider maps the incoming JSON-RPC parameters to a structured transaction request.
- Parameter Mapping: Converts hex values (gas, value, nonce) into
BigIntornumber. - Fee Detection: Automatically handles EIP-1559 (
maxFeePerGas) versus legacy (gasPrice) pricing based on the input. - Local Execution: Calls
internal.walletClient.sendTransactionusing the local account. - Broadcast: The resulting signed transaction hash is returned to the DApp.
Sources: src/provider.ts:251-277, src/types.ts:107-118
Message and Typed Data Signing
For signature requests like personal_sign or eth_signTypedData_v4, the provider extracts the message or data structure, uses the local walletClient to sign it, and returns the hex-encoded signature.
case "eth_signTypedData_v4": {
const typedDataJson = params?.[1] as string | TypedData;
const typedData: TypedData = typeof typedDataJson === "string"
? (JSON.parse(typedDataJson) as TypedData)
: typedDataJson;
const typedDataSignature = await internal.walletClient.signTypedData({
account: internal.account,
domain: typedData.domain,
types: typedData.types,
primaryType: typedData.primaryType,
message: typedData.message,
});
result = typedDataSignature as T;
break;
}
Sources: src/provider.ts:300-313, src/types.ts:133-144
Programmatic Control and Test Hooks
Local signing behavior can be modified dynamically during tests through control methods. This allows developers to simulate user interactions or edge cases.
Account Switching
The setSigningAccount method allows switching the active private key. It accepts a numeric index (0-9 for Anvil defaults), an address (lookup), a raw private key hex, or a viem Account object. When called, it recreates the internal WalletClient with the new credentials.
Sources: src/provider.ts:33-82, src/provider.ts:413-417
Signature and Transaction Rejection
To test error handling in DApps, the provider can be configured to simulate a user rejecting a request in a wallet.
setRejectTransaction(true): Interceptseth_sendTransactionand throws aProviderRpcErrorwith code4001.setRejectSignature(true): Intercepts signing methods (e.g.,personal_sign,eth_signTypedData) and throws code4001.
Configuration and Defaults
The system defaults to using the standard Anvil test accounts if no specific configuration is provided.
| Parameter | Default Value | Description |
|---|---|---|
account | 0xac0974...ff80 | The first Anvil private key |
chains | [mainnet] | Supported network configurations |
rpcUrls | {} | Mapping of chain IDs to RPC endpoints |
Sources: src/constants.ts:42-45, src/provider.ts:114-119, src/types.ts:26-39
Conclusion
Local Transaction Signing provides the bridge between standard DApp code and automated testing environments. By intercepting write-heavy EIP-1193 methods and processing them with local viem clients, it offers a high-speed, controllable alternative to manual wallet interaction while maintaining the realism of the underlying blockchain state via forwarded read calls.