Skip to Content
GuidesImpersonate a Mainnet Account

Impersonate a mainnet account

Send transactions from any mainnet address on your Stagenet — without its private key. Useful for calling owner-only functions on a deployed protocol, moving tokens out of a whale wallet, or reproducing a flow that depends on a specific production account.

Once impersonated, your Stagenet treats the address as if you owned its keys: any eth_sendTransaction with that address in from is accepted, and the EVM sees it as msg.sender.

Prerequisites

  • A Stagenet — create one in the dashboard if you don’t have one yet
  • The contract.dev CLI installed and configured at your project root (only required for the CLI snippets below):
npm install contract.dev npx contract.dev init --rpc-url=<YOUR_STAGENET_RPC_URL>

Start impersonating

From the CLI:

npx contract.dev impersonate 0x28C6c06298d514Db089934071355E5743bf21d60

From the SDK:

import { createStagenet } from "contract.dev"; const stagenet = createStagenet("<YOUR_STAGENET_RPC_URL>"); const whale = "0x28C6c06298d514Db089934071355E5743bf21d60"; await stagenet.impersonateAccount(whale);

The address is added to the Stagenet’s impersonation allowlist and stays there until you remove it.

Fund the account for gas

Impersonated accounts still pay gas. If the address has no native balance, top it up first.

The CLI accepts a --fund flag on the same command:

npx contract.dev impersonate 0x28C6... --fund "1 ether"

Or do it as a separate step:

npx contract.dev balance add 0x28C6... "1 ether"

From the SDK:

await stagenet.impersonateAccount(whale); await stagenet.addBalance(whale, 10n ** 18n); // 1 ETH for gas

Send transactions as the impersonated address

Impersonation only applies to eth_sendTransaction — unsigned transaction requests that go through the Stagenet RPC. It does not apply to eth_sendRawTransaction, because the sender of a raw transaction is already fixed by its signature.

Use a JSON-RPC signer that sends transaction requests through the Stagenet rather than signing locally:

ethers

import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider("<YOUR_STAGENET_RPC_URL>"); const whale = "0x28C6c06298d514Db089934071355E5743bf21d60"; const usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; const recipient = "0x1111111111111111111111111111111111111111"; const signer = await provider.getSigner(whale); const erc20 = new ethers.Contract( usdc, ["function transfer(address to, uint256 amount) returns (bool)"], signer, ); await erc20.transfer(recipient, 1_000_000n);

viem

import { createWalletClient, http } from "viem"; const stagenetUrl = "<YOUR_STAGENET_RPC_URL>"; const whale = "0x28C6c06298d514Db089934071355E5743bf21d60"; const wallet = createWalletClient({ account: whale, transport: http(stagenetUrl), }); await wallet.writeContract({ address: usdcAddress, abi: erc20Abi, functionName: "transfer", args: [recipient, 1_000_000n], });

If your wallet client signs locally with a private key, it will use eth_sendRawTransaction and impersonation will silently not apply. The two signers above (provider.getSigner(address) in ethers, createWalletClient({ account: address, ... }) in viem) both send unsigned requests through the RPC.

Impersonate several accounts at once

You can impersonate as many addresses as you need at the same time. Which address ends up as msg.sender is determined by the JSON-RPC signer you use, not by any “current” setting on the Stagenet.

await stagenet.impersonateAccount(alice); await stagenet.impersonateAccount(bob); const aliceSigner = await provider.getSigner(alice); const bobSigner = await provider.getSigner(bob); await myContract.connect(aliceSigner).vote(1); await myContract.connect(bobSigner).vote(2);

Stop impersonating

When you’re done, remove the address from the allowlist:

npx contract.dev impersonate stop 0x28C6...
await stagenet.stopImpersonatingAccount(whale);

After this, unsigned transactions from the address are rejected again.

To see who is currently impersonated:

npx contract.dev impersonate list
const accounts = await stagenet.getImpersonatedAccounts();

Next steps

Last updated on