Fix: Wagmi CLI Foundry Plugin Multiple Addresses with Same ABI
When building decentralized applications on Ethereum and EVM-compatible chains, developers frequently deploy multiple instances of the same contract blueprint. Whether you are launching dozens of ERC-20 token wrappers, NFT edition contracts, or independent vault instances, these deployments share the exact same ABI (Abstract Binary Interface) but reside at different contract addresses.
If you are using the @wagmi/cli tool along with its @wagmi/cli/plugins/foundry plugin, you will quickly hit a limitation: the Foundry plugin automatically scans your compiler artifacts and maps each Solidity contract name to a single generated React hook namespace. Trying to associate multiple addresses on the same chain with a single contract entry in your configuration file will result in JavaScript compilation failures or overwrite collisions.
Below, we detail how to resolve this limitation using two production-grade patterns.
If your compilation fails due to conflicting types or dependencies during this process, read our companion guide on Fixing Wagmi Foundry Plugin ABI Mismatch ERESOLVE Errors to align your build configurations.
The Problem: Solidity File-Name Key Collisions
In a standard wagmi.config.ts setup, the Foundry plugin is configured to inspect your project’s root files and automatically generate output hooks:
// wagmi.config.ts - COLLISION PRONE SETUP
import { defineConfig } from '@wagmi/cli'
import { foundry } from '@wagmi/cli/plugins'
export default defineConfig({
out: 'src/generated.ts',
plugins: [
foundry({
project: '../contracts',
// If we attempt to map multiple addresses to a single compiled artifact,
// the key mapping overrides itself on the same chain ID.
deployments: {
ZoraNFT: {
1: '0xAddressOne',
// 1: '0xAddressTwo' <-- SyntaxError: Duplicate object key
}
}
}),
],
})
Because the Foundry plugin generates TypeScript code matching the exact structure of your compiled files, the generated hooks (like useReadZoraNft) expect to either have a single hardcoded address or none at all.
Solution 1: The Runtime Address Hook Pattern (Recommended)
The cleanest, most architectural way to handle multiple contract deployments of the same ABI is to omit the address from the build configuration entirely.
If you do not define an address in the configuration, the Wagmi CLI generates generic “factory” hooks. These hooks require the target address as an input parameter whenever they are called on the frontend.
Step 1: Configure the CLI without Addresses
Modify your wagmi.config.ts to output the ABI and hooks without binding them to any specific address:
import { defineConfig } from '@wagmi/cli'
import { foundry } from '@wagmi/cli/plugins'
export default defineConfig({
out: 'src/generated.ts',
plugins: [
foundry({
project: '../contracts',
// No deployments block is declared here for ZoraNFT
}),
],
})
Step 2: Pass the Address Dynamically in React
When you call the generated hook in your application, you supply the address as an option. This allows a single hook to interact with an infinite number of deployed contract instances:
import { useReadZoraNft } from '../generated'
export function NFTDisplay({ tokenAddress }: { tokenAddress: `0x${string}` }) {
// Pass the target address at runtime
const { data: name, isLoading } = useReadZoraNft({
address: tokenAddress,
functionName: 'name',
})
if (isLoading) return <div>Loading metadata...</div>
return <div>Contract Name: {name}</div>
}
This pattern is highly recommended because it keeps your generated TypeScript files extremely lightweight and decouples your frontend logic from hardcoded deployment addresses.
Solution 2: The Shared ABI Aliased Contracts Pattern
If you prefer to have named, hardcoded references for key contracts (for example, if you have a GoldToken and a SilverToken that are both instances of the same ERC20.sol template), you can manually define separate objects in the contracts array.
By importing the ABI JSON file compiled by Foundry directly, you can create multiple unique configurations that share the same underlying ABI.
Step 1: Map Aliased Configurations
Configure your wagmi.config.ts as follows:
import { defineConfig } from '@wagmi/cli'
import { react } from '@wagmi/cli/plugins'
// Import the ABI JSON compiled by Foundry
import ZoraNFTArtifact from '../contracts/out/ZoraNFT.sol/ZoraNFT.json' assert { type: 'json' }
export default defineConfig({
out: 'src/generated.ts',
contracts: [
{
name: 'ZoraNFTMain',
abi: ZoraNFTArtifact.abi as any,
address: {
1: '0xAddressOne',
11155111: '0xAddressOneSepolia',
},
},
{
name: 'ZoraNFTEditionTwo',
abi: ZoraNFTArtifact.abi as any,
address: {
1: '0xAddressTwo',
11155111: '0xAddressTwoSepolia',
},
},
],
plugins: [
react(), // Generate hooks for the contracts array
],
})
Step 2: Use Named Hooks in Components
The Wagmi CLI will generate unique, dedicated hooks for each named entry, making it clear which deployment is being targeted:
import { useReadZoraNftMain, useReadZoraNftEditionTwo } from '../generated'
export function Dashboard() {
const { data: nameMain } = useReadZoraNftMain({ functionName: 'name' })
const { data: nameEdition } = useReadZoraNftEditionTwo({ functionName: 'name' })
return (
<div>
<p>Main Hub: {nameMain}</p>
<p>Edition Two: {nameEdition}</p>
</div>
)
}
Summary of Best Practices
- Use Solution 1 (Runtime Address) if the list of addresses is dynamic, large, or fetched from a database/index.
- Use Solution 2 (Shared ABI Aliases) if you only have a few key static deployments and want type safety with pre-bound addresses.
- Never try to duplicate keys inside the
deploymentsmapping block of the@wagmi/cli/plugins/foundryplugin.