Skip to content

How to Use Viem Integration

Hardhat-deploy provides @rocketh/viem a small extension for Viem that gives you access to a viem client and mechanism to get deployed contracts by name with full type safety.

Prerequisites

Install the viem extension:

bash
npm install -D @rocketh/viem
bash
pnpm add -D @rocketh/viem

Basic Configuration

Add viem support to your rocketh/config.ts by importing and spreading the extension:

typescript
import type {UserConfig} from 'rocketh/types';

// we define our config and export it as "config"
export const config = {
	accounts: {
		deployer: {
			default: 0,
		},
		admin: {
			default: 1,
		},
	},
	data: {},
} as const satisfies UserConfig;

// this one provide a viem handle to clients and contracts
import * as viemExtension from '@rocketh/viem';

// and export them as a unified object
const extensions = {
	...viemExtension,
};
export {extensions};

// then we also export the types that our config ehibit so other can use it

type Extensions = typeof extensions;
type Accounts = typeof config.accounts;
type Data = typeof config.data;

export type {Extensions, Accounts, Data};

Template Import Pattern

The template-ethereum-contracts shows the clean import pattern:

typescript
import {deployScript, artifacts} from '../rocketh/deploy.js';

export default deployScript(async (env) => {
	// env.viem is now available
	const contract = env.viem.getContract<MyAbi>('ContractName');
});

Viem Environment API

The viem extension adds several utilities to the environment:

env.viem.getContract<T>(deployment | name)

Get a type-safe contract instance from a deployment. if you provide a name only (as opposed to a typed deployment object), you will need to provide the generic type from the generated ABI since getContract cannot infer the type from the deployment name alone:

typescript
import {deployScript, artifacts} from '../rocketh/deploy.js';

export default deployScript(
	async (env) => {
		const {deployer} = env.namedAccounts;

		const deployment = await env.deploy('GreetingsRegistry', {
			account: deployer,
			artifact: artifacts.GreetingsRegistry,
			args: ['Hello, World!'],
		});

		// Get type-safe contract instance from the deployment
		const contract = env.viem.getContract(deployment);

		// Get type-safe contract instance by passing the abi type as generic
		const contract2 = env.viem.getContract<typeof artifacts.GreetingsRegistry.abi>('GreetingsRegistry');

		// Type-safe read operations
		const message = await contract.read.messages([deployer]);
		console.log('Current message:', message);

		// Get contract info
		console.log('Contract address:', contract.address);
		console.log('Contract ABI available:', !!contract.abi);
	},
	{tags: ['GreetingsRegistry']},
);

env.viem.publicClient

Access the viem public client for network operations:

typescript
import {deployScript, artifacts} from '../rocketh/deploy.js';

export default deployScript(
	async (env) => {
		const {deployer} = env.namedAccounts;

		// Get network information
		const blockNumber = await env.viem.publicClient.getBlockNumber();
		const chainId = await env.viem.publicClient.getChainId();

		console.log(`Deploying on chain ${chainId} at block ${blockNumber}`);

		// Check account balance
		const balance = await env.viem.publicClient.getBalance({
			address: deployer as `0x${string}`,
		});

		console.log(`Deployer balance: ${balance} wei`);

		// Deploy contract
		const deployment = await env.deploy('MyContract', {
			account: deployer,
			artifact: artifacts.MyContract,
			args: [],
		});
	},
	{tags: ['MyContract']},
);

Contract Interactions

Reading Contract State

Use the type-safe contract instance for reading:

typescript
import {deployScript, artifacts} from '../rocketh/deploy.js';

export default deployScript(
	async (env) => {
		const {deployer} = env.namedAccounts;

		const deployment = await env.deploy('Counter', {
			account: deployer,
			artifact: artifacts.Counter,
			args: [0],
		});

		const contract = env.viem.getContract(deployment);

		// Type-safe read operations
		const count = await contract.read.count();
		const owner = await contract.read.owner();

		console.log(`Count: ${count}`);
		console.log(`Owner: ${owner}`);

		// Read with parameters
		const isAuthorized = await contract.read.isAuthorized([deployer as `0x${string}`]);
		console.log(`Deployer authorized: ${isAuthorized}`);
	},
	{tags: ['Counter']},
);

Transaction Execution

Important: For transaction execution, use env.execute() instead of direct viem calls. The @rocketh/read-execute extension keeps track of pending transactions, while viem client will not.

typescript
import {deployScript, artifacts} from '../rocketh/deploy.js';

export default deployScript(
	async (env) => {
		const {deployer, admin} = env.namedAccounts;

		const deployment = await env.deploy('MyToken', {
			account: deployer,
			artifact: artifacts.MyToken,
			args: ['My Token', 'MTK'],
		});

		// ✅ RECOMMENDED: Use env.execute() for transactions
		// This tracks pending transactions and handles nonce management
		await env.execute(deployment, {
			functionName: 'transfer',
			args: [admin, 1000n],
			account: deployer,
		});

		// ❌ NOT RECOMMENDED: Direct viem wallet client usage
		// This doesn't track pending transactions
		// const contract = env.viem.getContract(deployment);
		// await contract.write.transfer([admin, 1000n], { account: deployer });

		console.log('Transfer completed using env.execute()');
	},
	{tags: ['MyToken']},
);

Why Use env.execute() Over Direct Viem?

The @rocketh/read-execute extension provides several advantages:

  1. Pending Transaction Tracking: Keeps track of pending transactions to avoid nonce conflicts
  2. Deployment Integration: Works seamlessly with hardhat-deploy's deployment system
  3. Consistent Logging: Provides consistent transaction logging and receipts
typescript
// env.execute() handles all of this automatically:
// - Nonce management
// - Gas estimation
// - Transaction tracking
// - Receipt handling
// - Error reporting

await env.execute(deployment, {
	functionName: 'setMessage',
	args: ['New message'],
	account: deployer,
	// Optional: gas limit, gas price, etc.
});

Next Steps