Overview

The SmartContractManager is a high-level manager for deploying contracts using CREATE2, executing contract calls, and orchestrating contract state for tests. It handles the complexity of contract deployment and interaction, allowing you to focus on test logic rather than blockchain plumbing.

Constructor

new SmartContractManager(projectRoot: string)
Creates a new SmartContractManager instance.
projectRoot
string
required
Path to the root of the project (used to locate contract artifacts)

Properties

projectRoot
string
Root directory for artifact lookup
publicClient
PublicClient
viem public client (initialized in initialize)
walletClient
WalletClient
viem wallet client (initialized in initialize)
deployedContracts
Map<Address, Abi>
Map of deployed contract addresses to their ABIs
proxyDeployer
ProxyDeployer
Instance of ProxyDeployer for managing the deployment proxy

Methods

initialize()

async initialize(node: LocalNodeManager): Promise<void>
Initializes viem clients and ensures the proxy is deployed. Must be called before using other methods.
node
LocalNodeManager
required
The local node manager instance

deployContract()

async deployContract(deployment: ContractDeployment): Promise<Address>
Deploys a contract using CREATE2 and stores its ABI.
deployment
ContractDeployment
required
Contract deployment configuration
Returns: The deployed contract address

executeCall()

async executeCall(call: ContractCall): Promise<Hex>
Executes a contract function call as a transaction.
call
ContractCall
required
Contract call configuration
Returns: The transaction hash

setContractState()

async setContractState(
  config: SetupConfig, 
  node: LocalNodeManager
): Promise<void>
Deploys contracts and executes calls as specified in a setup config. This is useful for setting up complex test scenarios.
config
SetupConfig
required
Setup configuration with deployments and calls
node
LocalNodeManager
required
The local node manager instance

predictContractAddress()

predictContractAddress(
  salt: Hex, 
  bytecode: Hex, 
  args: readonly unknown[]
): Address
Predicts the address for a CREATE2 deployment without actually deploying.
salt
Hex
required
32-byte salt for CREATE2
bytecode
Hex
required
Contract bytecode
args
readonly unknown[]
required
Constructor arguments
Returns: The predicted contract address

Contract Artifacts

The SmartContractManager loads contract artifacts from your project’s build directory. It supports:
  • Foundry: Looks for artifacts in out/ directory
  • Hardhat: Looks for artifacts in artifacts/ directory

Artifact Structure

Artifacts should contain:
{
  abi: [...],        // Contract ABI
  bytecode: "0x..." // Contract bytecode
}

Complete Examples

Basic Token Deployment

import { SmartContractManager } from '@coinbase/onchaintestkit/contracts/SmartContractManager';
import { LocalNodeManager } from '@coinbase/onchaintestkit/node/LocalNodeManager';

async function deployToken() {
  const node = new LocalNodeManager({ chainId: 31337 });
  await node.start();

  try {
    const scm = new SmartContractManager(process.cwd());
    await scm.initialize(node);

    // Deploy ERC20 token
    const tokenAddress = await scm.deployContract({
      name: 'ERC20Token',
      args: ['Test Token', 'TEST', 1000000n * 10n ** 18n],
      salt: '0x' + '01'.repeat(32),
      deployer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    });

    console.log(`Token deployed at: ${tokenAddress}`);

    // Mint tokens
    await scm.executeCall({
      target: tokenAddress,
      functionName: 'mint',
      args: ['0x70997970C51812dc3A010C7d01b50e0d17dc79C8', 1000n * 10n ** 18n],
      account: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    });

  } finally {
    await node.stop();
  }
}

Best Practices

1

Initialize before use

Always call initialize() before using other methods
2

Use deterministic salts

Generate salts based on test names or contract purposes for reproducibility
3

Handle deployment errors

Wrap deployments in try-catch blocks to handle failures gracefully
4

Clean up resources

Always stop the node after tests complete

Tips and Tricks

Use the predictContractAddress method to get addresses before deployment. This is useful for setting up circular dependencies.
The SmartContractManager keeps track of deployed contracts’ ABIs, making it easier to interact with them later.
Contract addresses depend on bytecode. Changing compiler settings or contract code will result in different addresses.

See Also