Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Account Impersonation

Overview

Account impersonation allows you to send transactions from any address without possessing its private key. This is invaluable for testing scenarios involving:

  • Whale accounts: Test large transfers without controlling the actual whale's key
  • DAO contracts: Simulate governance actions from multisig or timelock contracts
  • Protocol admins: Test admin-only functions from privileged addresses
  • External users: Simulate realistic user interactions in integration tests

Using impersonate() with try-with-resources

The recommended way to use impersonation is with Java's try-with-resources pattern. This ensures the impersonation is automatically stopped when the session ends, preventing resource leaks.

import sh.brane.rpc.Brane;
import sh.brane.rpc.ImpersonationSession;
import sh.brane.core.types.Address;
import sh.brane.core.types.Wei;
import sh.brane.core.model.TransactionRequest;
 
Brane.Tester tester = Brane.connectTest();
 
// Address to impersonate (no private key needed)
Address whale = new Address("0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8");
 
// Fund the address first (impersonation doesn't create ETH)
tester.setBalance(whale, Wei.fromEther(new java.math.BigDecimal("10000")));
 
// Impersonate with automatic cleanup
try (ImpersonationSession session = tester.impersonate(whale)) {
    var request = TxBuilder.eip1559()
        .to(recipient)
        .value(Wei.fromEther(new java.math.BigDecimal("1000")))
        .build();
 
    // Transaction is sent from the whale address
    TransactionReceipt receipt = session.sendTransactionAndWait(request);
    System.out.println("Transfer confirmed: " + receipt.transactionHash());
}
// Impersonation automatically stopped when session closes

ImpersonationSession API

The ImpersonationSession interface provides methods for sending transactions as the impersonated address:

MethodDescription
address()Returns the impersonated address
sendTransaction(request)Sends a transaction, returns immediately with hash
sendTransactionAndWait(request)Sends and waits for confirmation (60s timeout)
sendTransactionAndWait(request, timeout, poll)Sends with custom timeout settings
close()Stops impersonation (called automatically with try-with-resources)

How ImpersonationSession Works

When you call tester.impersonate(address):

  1. Brane calls the test node's impersonation RPC method (e.g., anvil_impersonateAccount)
  2. The test node allows transactions from that address without signature verification
  3. An ImpersonationSession is returned that wraps the impersonated address
  4. Transactions sent via the session use eth_sendTransaction with from set to the impersonated address
  5. When the session closes, Brane calls the stop impersonation RPC (e.g., anvil_stopImpersonatingAccount)
// The session automatically sets the 'from' field
try (ImpersonationSession session = tester.impersonate(whale)) {
    // This request doesn't need to specify 'from'
    var request = TxBuilder.eip1559()
        .to(recipient)
        .value(Wei.fromEther(new java.math.BigDecimal("100")))
        .data(calldata)
        .build();
 
    // Session adds from=whale automatically
    Hash txHash = session.sendTransaction(request);
}

Whale Testing Pattern

A common use case is testing interactions with whale accounts - addresses holding large token balances on mainnet. Here's a complete pattern:

import sh.brane.rpc.Brane;
import sh.brane.rpc.ImpersonationSession;
import sh.brane.rpc.SnapshotId;
import sh.brane.core.types.Address;
import sh.brane.core.types.Wei;
 
public class WhaleTestExample {
    // Known whale addresses (example: Binance hot wallet)
    private static final Address BINANCE_WHALE =
        new Address("0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8");
 
    public void testLargeTransfer(Brane.Tester tester) {
        // 1. Take snapshot for test isolation
        SnapshotId snapshot = tester.snapshot();
        try {
            Address recipient = new Address("0x...");
 
            // 2. Fund the whale with ETH for gas
            tester.setBalance(BINANCE_WHALE, Wei.fromEther(new java.math.BigDecimal("100")));
 
            // 3. Impersonate and execute
            try (ImpersonationSession session = tester.impersonate(BINANCE_WHALE)) {
                var request = TxBuilder.eip1559()
                    .to(recipient)
                    .value(Wei.fromEther(new java.math.BigDecimal("50")))
                    .build();
 
                TransactionReceipt receipt = session.sendTransactionAndWait(request);
 
                // 4. Verify the transfer
                BigInteger recipientBalance = tester.getBalance(recipient);
                assert recipientBalance.compareTo(Wei.fromEther(new java.math.BigDecimal("50")).value()) >= 0;
            }
        } finally {
            // 5. Restore state for next test
            tester.revert(snapshot);
        }
    }
}

Forking with Real Whale Balances

For more realistic testing, fork from mainnet to use actual whale balances:

// Fork mainnet at a specific block
tester.reset("https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY", 18_000_000L);
 
// Now impersonate a real whale with their actual balance
Address usdcWhale = new Address("0x...");
try (ImpersonationSession session = tester.impersonate(usdcWhale)) {
    // Whale already has tokens - no need to set balance
    // Execute ERC20 transfer...
}

sendImpersonatedTransaction

For simple one-off transactions, you can manually manage impersonation without the session pattern. However, the try-with-resources pattern is strongly recommended.

Manual Impersonation (Not Recommended)

Address target = new Address("0x...");
 
// Start impersonation
ImpersonationSession session = tester.impersonate(target);
try {
    // Send transaction
    var request = TxBuilder.eip1559()
        .to(recipient)
        .value(Wei.fromEther(new java.math.BigDecimal("1")))
        .build();
    Hash txHash = session.sendTransaction(request);
 
    // Wait for confirmation
    TransactionReceipt receipt = tester.waitForReceipt(txHash, 60_000, 1_000);
} finally {
    // Must manually close!
    session.close();
}

stopImpersonating

You can also stop impersonation directly via the tester:

Address target = new Address("0x...");
 
// Start impersonation (without session)
tester.impersonate(target);
 
// ... do work ...
 
// Stop impersonation explicitly
tester.stopImpersonating(target);

Auto-Impersonation (Anvil Only)

Anvil supports automatic impersonation where any address can send transactions without explicit impersonation calls. This is useful for rapid prototyping but should be used carefully.

// Enable auto-impersonate (Anvil only)
tester.enableAutoImpersonate();
 
// Now any address can send transactions via eth_sendTransaction
// without calling impersonate() first
 
// Disable when done
tester.disableAutoImpersonate();

Test Node Compatibility

Impersonation works across all supported test nodes with different RPC method prefixes:

OperationAnvilHardhatGanache
Start impersonationanvil_impersonateAccounthardhat_impersonateAccountevm_impersonateAccount
Stop impersonationanvil_stopImpersonatingAccounthardhat_stopImpersonatingAccountevm_stopImpersonatingAccount
Auto-impersonateanvil_autoImpersonateAccountNot supportedNot supported

The correct RPC method is selected automatically based on TestNodeMode.

Common Patterns

Testing Contract Admin Functions

// Impersonate the contract owner
Address owner = new Address("0x..."); // Read from contract
try (ImpersonationSession session = tester.impersonate(owner)) {
    // Call admin-only function
    HexData calldata = abi.encode("setFee", newFee);
    var request = TxBuilder.eip1559()
        .to(contractAddress)
        .data(calldata)
        .build();
    session.sendTransactionAndWait(request);
}

Testing DAO Governance

// Impersonate a timelock contract
Address timelock = new Address("0x...");
tester.setBalance(timelock, Wei.fromEther(new java.math.BigDecimal("1"))); // For gas
 
try (ImpersonationSession session = tester.impersonate(timelock)) {
    // Execute queued governance proposal
    HexData executeCalldata = abi.encode("execute", proposalId);
    var request = TxBuilder.eip1559()
        .to(governorAddress)
        .data(executeCalldata)
        .build();
    session.sendTransactionAndWait(request);
}

Multiple Impersonations

You can have multiple impersonation sessions, but only one per address:

Address alice = new Address("0xAlice...");
Address bob = new Address("0xBob...");
 
tester.setBalance(alice, Wei.fromEther(new java.math.BigDecimal("10")));
tester.setBalance(bob, Wei.fromEther(new java.math.BigDecimal("10")));
 
// Sequential impersonations
try (ImpersonationSession aliceSession = tester.impersonate(alice)) {
    aliceSession.sendTransactionAndWait(aliceRequest);
}
 
try (ImpersonationSession bobSession = tester.impersonate(bob)) {
    bobSession.sendTransactionAndWait(bobRequest);
}

Error Handling

RpcException on Impersonation Failure

try (ImpersonationSession session = tester.impersonate(address)) {
    session.sendTransactionAndWait(request);
} catch (RpcException e) {
    // Handle RPC errors (network issues, invalid params)
    System.err.println("RPC error: " + e.getMessage());
} catch (RevertException e) {
    // Handle transaction reverts
    System.err.println("Transaction reverted: " + e.getMessage());
}

Session Already Closed

ImpersonationSession session = tester.impersonate(address);
session.close();
 
// This throws IllegalStateException
session.sendTransaction(request); // ERROR: ImpersonationSession has been closed

Best Practices

Always Use try-with-resources

// Good - automatic cleanup
try (ImpersonationSession session = tester.impersonate(address)) {
    session.sendTransactionAndWait(request);
}
 
// Bad - manual cleanup prone to leaks
ImpersonationSession session = tester.impersonate(address);
session.sendTransactionAndWait(request);
session.close(); // Easy to forget, especially with exceptions

Fund Addresses Before Impersonating

// Good - fund first
tester.setBalance(whale, Wei.fromEther(new java.math.BigDecimal("100")));
try (ImpersonationSession session = tester.impersonate(whale)) {
    session.sendTransactionAndWait(request);
}
 
// Bad - transaction may fail with insufficient funds
try (ImpersonationSession session = tester.impersonate(whale)) {
    session.sendTransactionAndWait(request); // May fail!
}

Combine with Snapshots for Isolation

SnapshotId snapshot = tester.snapshot();
try {
    tester.setBalance(whale, Wei.fromEther(new java.math.BigDecimal("1000")));
    try (ImpersonationSession session = tester.impersonate(whale)) {
        session.sendTransactionAndWait(request);
    }
    // Assert results...
} finally {
    tester.revert(snapshot); // Clean state for next test
}

Document the Address Being Impersonated

// Good - clear context
// Impersonate Uniswap V3 Router for swap testing
Address uniswapRouter = new Address("0xE592427A0AEce92De3Edee1F18E0157C05861564");
try (ImpersonationSession session = tester.impersonate(uniswapRouter)) {
    // ...
}