This comprehensive technical guide documents the monorepo setup, build system architecture, and patterns that future agents need to understand to work effectively with the Zora Protocol codebase.
For day-to-day development commands and workflows, see CLAUDE.md.
- Build System Architecture
- Package Categorization & Patterns
- Build Optimization Learnings
- Monorepo-Specific Gotchas
- Developer Workflow Patterns
- Dependency Chain Analysis
- Performance Optimization Strategies
- Common Failure Patterns and Solutions
- How to Publish a Contract in protocol-deployments
- Best Practices for Future Development
The monorepo uses Turbo for orchestration with distinct build task patterns:
build: Full contract compilation + TypeScript builds + ABI generationbuild:js: Minimal contract compilation + wagmi generation + TypeScript output onlybuild:contracts:minimal: Forge compilation without tests, scripts, or metadatabuild:site: Documentation site builds (depends onbuild:js)
Key Insight: The build:js task was introduced specifically for wagmi consumption and documentation builds to avoid unnecessary contract compilation overhead.
- Documentation builds (
build:sitedepends on^build:js) - CI/CD JavaScript testing pipeline
- Package consumption by external tools (wagmi, SDK generation)
- Development workflows that only need TypeScript/ABI outputs
- Release preparation (
pnpm releaseusesturbo run build) - Contract testing and deployment
- Gas reporting and coverage analysis
- Storage layout verification
Critical Dependencies:
- The internal generation package's
build:jsdepends on^build:jsfrom all contract packages - The published deployment package's
build:jsdepends specifically on the internal generation package - Documentation builds use
build:jsto avoid unnecessary Solidity compilation
Contract packages are identified by the presence of foundry.toml and wagmi.config.ts files. These packages follow a hybrid compilation pattern supporting both Solidity contract development and TypeScript consumption. Examples include packages focused on core protocol contracts, commenting systems, shared contract utilities, and wallet implementations.
Identification Pattern: Look for packages containing both:
foundry.toml- Foundry configuration for Solidity compilationwagmi.config.ts- Wagmi configuration for TypeScript ABI generation
Build Pattern:
These packages focus on consumption APIs, tooling, and build utilities. They contain only TypeScript/JavaScript code without Solidity contracts. Examples include SDK packages for contract interaction, deployment configuration packages, internal code generation utilities, and shared build tooling.
Identification Pattern: Look for packages with:
tsconfig.jsonortsup.config.tsbut nofoundry.toml- Primary focus on JavaScript/TypeScript build outputs
- Often serve as consumption layers or development tooling
Build Pattern:
{
"build": "pnpm tsup",
"build:js": "pnpm run build"
}Key Insight: Legacy packages maintain backward compatibility but follow similar patterns to active packages after the build optimization.
Legacy packages in the legacy/ directory represent previous iterations of protocol components. They maintain the same architectural patterns as active packages (contract + TypeScript hybrids or TypeScript-only) but are no longer the primary development focus. These packages often represent superseded implementations or earlier protocol versions that remain for compatibility.
Identification Pattern:
- Located in
legacy/directory - May follow older build patterns but have been updated for performance
- Often have newer equivalents in the main
packages/directory
Legacy Build Evolution: Recent commits show legacy packages were updated to use build:contracts:minimal instead of full FOUNDRY_PROFILE=dev forge build, significantly improving build performance.
Documentation packages are typically found in the root directory and focus on static site generation. They depend on TypeScript packages for type information and use frameworks like Vocs for MDX processing.
Identification Pattern:
- Located in root directory or dedicated docs folders
- Contain
vocs.config.tsor similar static site configuration - Depend on
^build:jsfrom SDK packages for type generation
Build Dependencies: Documentation builds depend on build:js from SDK packages, ensuring they have access to generated types without triggering expensive contract compilation.
Problem: Documentation builds and CI were running full contract compilation when they only needed TypeScript types and ABIs.
Solution: Introduction of build:contracts:minimal pattern:
# Before (slow)
FOUNDRY_PROFILE=dev forge build && wagmi generate
# After (fast)
pnpm run build:contracts:minimal && wagmi generate
# Where build:contracts:minimal = "forge build --skip test --skip script --no-metadata"Performance Impact:
- Skips test compilation (major time saver)
- Skips script compilation
- Removes metadata generation
- Reduces build artifacts by ~70%
- Maintains full wagmi compatibility
- Full Build (
forge build): All contracts, tests, scripts, metadata - Minimal Build (
forge build --skip test --skip script --no-metadata): Production contracts only - Size Analysis (
forge build --sizes): Contract size reporting - Dev Profile (
FOUNDRY_PROFILE=dev forge build): Legacy pattern, now replaced
Turbo Caching Strategy:
- All build tasks cache outputs to
dist/**,out/**,abis/** devtask explicitly disables cache ("cache": false) and runs persistently- Cache keys include package dependencies and source file changes
- Documentation builds benefit from cached
build:jsoutputs
Critical Pattern: The protocol-deployments system uses a sophisticated three-stage dependency isolation pipeline:
Contract Packages → protocol-deployments-gen → protocol-deployments
Architectural Goal: Dependency-Free Published Package
The entire architecture is designed around a core constraint: protocol-deployments must be dependency-free for external consumption. This clean published package allows external developers to import Zora Protocol ABIs and addresses without pulling in the entire monorepo's transitive dependencies.
Separation of Concerns:
-
protocol-deployments-gen(Internal Build Tool):- Has dependencies on internal/non-published monorepo packages
- Aggregates ABIs and deployment addresses from ALL contract packages
- Pulls data from both
packages/andlegacy/directories - Bundles everything into generated code files
- Never published to npm - purely internal tooling
-
protocol-deployments(Clean Published Package):- Zero dependencies in package.json
- Contains only generated code from the -gen package
- Published to npm for external consumption
- Provides wagmi-compatible types and ABIs
- Clean API surface for external developers
Generation Process:
- Contract packages compile and generate ABIs via wagmi
protocol-deployments-genimports ABIs from all contract packages using workspace references- The -gen package bundles ABIs with deployment addresses into consolidated files
- Generated code is written directly into
protocol-deploymentspackage protocol-deploymentsbuilds and publishes the generated code with no dependencies
Why This Pattern Matters:
- External Consumption: Wagmi users get a clean package without monorepo complexity
- Dependency Isolation: No risk of version conflicts from internal tooling
- Maintainability: Internal refactoring doesn't break external consumers
- Performance: Smaller dependency graph for external applications
- Monorepo Flexibility: Internal packages can change without affecting public API
Common Failure: If you update a contract package but don't trigger protocol-deployments-gen's build:js, the published protocol-deployments package won't include the new ABIs. The generation step is critical for propagating changes.
Complex Wagmi Setup: The internal generation package's wagmi.config.ts imports from ALL contract packages in both packages/ and legacy/ directories.
// Pattern: imports from all contract packages
import * as abis from "@zoralabs/package-name";
import { specificABI } from "@zoralabs/another-package";
// ... imports from all packages with foundry.tomlGotcha: This configuration file must be manually updated when contract packages add new exports or when new contract packages are added to the monorepo.
Turbo Handles Most Ordering, but be aware:
- Internal generation packages (*-gen) MUST build before their corresponding published packages
- Documentation packages need SDK packages built first
- Wagmi generation requires contract compilation to complete
- Shared utilities must build before packages that depend on them
Legacy Issue: Old packages used FOUNDRY_PROFILE=dev but this is being phased out in favor of explicit forge flags.
Best Practice: Use explicit forge flags rather than profiles:
# Good
forge build --skip test --skip script
# Avoid (legacy pattern)
FOUNDRY_PROFILE=dev forge buildDevelopment (Fast Iteration):
pnpm build:js # TypeScript + minimal contracts
pnpm dev # Watch mode for testingDocumentation Work:
pnpm build:docs:coins # Builds site with optimized dependencies
pnpm docs:preview # Local previewContract Development:
cd packages/coins
forge test -vv # Test specific package
pnpm build # Full build with ABIsRelease Preparation:
pnpm build # Full compilation of everything
pnpm test # Complete test suite
pnpm changeset # Version managementFor complete command reference, see CLAUDE.md.
Contract Testing: Foundry-based per package
cd packages/coins
forge test -vvv
forge test --watch -vvv # Watch modeIntegration Testing: TypeScript-based
pnpm test:integration # In SDK packagesCoverage Analysis:
pnpm run coverage # Generates LCOV reportsFor detailed testing workflows, see CLAUDE.md.
Two Documentation Sites:
docs/: Coins protocol (primary)nft-docs/: Legacy NFT protocol
Build Process:
- Documentation depends on
^build:jsfrom SDK packages - Vocs builds static sites with MDX processing
- TypeScript types are generated from wagmi ABIs
- Sites deploy to Vercel with optimized caching
Performance Optimization: Documentation builds avoid contract compilation by depending only on build:js, reducing build time from ~5min to ~30sec.
High-Level Flow:
Contract Packages (packages/*/ with foundry.toml)
↓ (ABIs + addresses)
Internal Generation Package (*-gen)
↓ (consolidated wagmi types)
Published Deployment Package
↓ (published package)
SDK Packages, Documentation Sites
Workspace Dependencies (uses workspace:^):
- Most packages depend on shared TypeScript configuration
- Contract packages depend on shared build tooling and scripts
- SDK packages depend on the main protocol deployments package
- Documentation sites depend on SDK packages for type information
- Shared Build Utilities: Common tooling used across all contract packages
- Internal Generation Package: Aggregation point for all contract ABIs (typically named *-gen)
- Published Deployment Package: Public API for external contract interactions
Failure Points:
- If shared build utilities fail, all contract package builds fail
- If the internal generation package fails, no external packages get updated ABIs
- If shared TypeScript configuration fails, all TypeScript builds fail
- Minimal Contract Builds: Use
--skip test --skip script --no-metadatafor wagmi generation - Targeted Documentation Builds: Use
build:jsdependencies to avoid unnecessary compilation - Incremental TypeScript: tsup with
onSuccesshooks for declaration generation - Turbo Caching: Proper output declarations for effective caching
JavaScript Pipeline: Uses pnpm turbo run build:js instead of full build
Contract Pipeline: Full build only for contract-specific changes
Documentation Pipeline: Optimized builds with wagmi-only dependencies
Large Packages: 1155-contracts and coins are memory-intensive due to complex Solidity compilation
Parallel Builds: Turbo runs compatible packages in parallel
Resource Limits: Some packages may need --max_old_space_size for Node.js
Cause: Package dependencies not built in correct order
Solution: Ensure ^build:js dependencies are correct in turbo.json
Cause: Internal generation package not rebuilt after contract changes
Solution: Run pnpm turbo run build:js from root
Cause: Missing TypeScript types from SDK packages
Solution: Verify docs depend on ^build:js from required SDKs
Cause: Large contract dependencies (especially Uniswap V4)
Solution: Use build:contracts:minimal or increase Node memory
Cause: Turbo cache not recognizing source changes
Solution: Clear cache with turbo run build --force
This section provides step-by-step instructions for adding new contracts to the published @zoralabs/protocol-deployments package, making them available for wagmi consumption by external developers.
The protocol-deployments system uses a three-stage pipeline to convert individual contract packages into a clean, dependency-free published package:
1. Individual Package (e.g., coins/)
└── wagmi.config.ts defines which contracts to export
└── addresses/*.json files contain deployment addresses
2. protocol-deployments-gen/ (Internal Generation Tool)
└── wagmi.config.ts imports ABIs from all packages
└── Combines ABIs with addresses into consolidated wagmi types
└── Outputs generated/wagmi.ts
3. protocol-deployments/ (Published Package)
└── copy-generated script copies the generated file
└── Zero dependencies for clean external consumption
In your contract package (e.g., packages/coins/wagmi.config.ts), add the contract name to the include array:
// packages/coins/wagmi.config.ts
export default defineConfig({
out: "package/wagmiGenerated.ts",
plugins: [
foundry({
forge: {
build: false,
},
include: [
"BaseCoin", // Existing contract
"CreatorCoin", // Existing contract
"YourNewContract", // ← Add your new contract here
// ... other contracts
].map((contractName) => `${contractName}.json`),
}),
],
});Key Points:
- Only include contracts that should be publicly available
- Contract names must match the Solidity contract names exactly
- The
.jsonextension is added automatically by the.map()function
This is the most complex step. You need to update /packages/protocol-deployments-gen/wagmi.config.ts to import your new ABI and handle it appropriately.
Add your contract ABI to the imports at the top of the file:
// At the top of protocol-deployments-gen/wagmi.config.ts
import {
zoraFactoryImplABI,
baseCoinABI,
creatorCoinABI,
yourNewContractABI, // ← Add this import
// ... other imports
} from "@zoralabs/coins"; // or your package nameFind or create the appropriate getter function for your package. For example, coins contracts go in getCoinsContracts():
For Address-Based Contracts (deployed contracts with known addresses):
const getCoinsContracts = (): ContractConfig[] => {
const addresses: Addresses = {};
// ... existing address loading logic ...
// Add your contract with addresses
addAddress({
abi: yourNewContractABI,
addresses,
configKey: "YOUR_NEW_CONTRACT", // Must match key in addresses/*.json
contractName: "YourNewContract", // Name in final wagmi types
storedConfigs,
});
return [
...toConfig(addresses),
// ... existing ABI-only contracts ...
];
};For ABI-Only Contracts (interfaces, no deployment addresses needed):
const getCoinsContracts = (): ContractConfig[] => {
// ... existing address-based contracts ...
return [
...toConfig(addresses),
{
abi: yourNewContractABI,
name: "YourNewContract",
},
// ... other ABI-only contracts ...
];
};Some contracts combine errors from multiple ABIs. Follow this pattern if your contract needs error handling:
addAddress({
abi: [
...yourNewContractABI,
...extractErrors(someOtherContractABI), // Include related errors
],
addresses,
configKey: "YOUR_NEW_CONTRACT",
contractName: "YourNewContract",
storedConfigs,
});If your contract has deployment addresses, create address files in your package's addresses/ directory:
File Format: packages/your-package/addresses/{chainId}.json
{
"YOUR_NEW_CONTRACT": "0x1234567890123456789012345678901234567890",
"EXISTING_CONTRACT": "0x...",
"OTHER_ADDRESSES": "0x..."
}Examples:
packages/coins/addresses/8453.json(Base mainnet)packages/coins/addresses/1.json(Ethereum mainnet)packages/coins/addresses/dev/31337.json(Local development)
Key Requirements:
- Use uppercase with underscores for JSON keys
- Keys must match the
configKeyused inaddAddress()calls - Addresses must be valid hex strings with
0xprefix
Execute these commands in the correct order to propagate your changes:
# 1. Build your package to generate fresh ABIs
pnpm --filter @zoralabs/your-package build:js
# 2. Build the generation package to create consolidated wagmi types
pnpm --filter @zoralabs/protocol-deployments-gen build:js
# 3. Build the published package to copy generated files
pnpm --filter @zoralabs/protocol-deployments build:jsAlternative: Build all related packages at once:
pnpm turbo run build:js --filter="@zoralabs/protocol-deployments*" --filter="@zoralabs/your-package"After completing the generation process, verify your contract is properly published:
-
Check Generated File:
# Look for your contract in the generated wagmi types cat packages/protocol-deployments-gen/generated/wagmi.ts | grep -A 10 "YourNewContract"
-
Verify in Published Package:
# Ensure the generated file was copied cat packages/protocol-deployments/src/generated/wagmi.ts | grep "YourNewContract"
-
Test Import:
// Test that external consumers can import your contract import { yourNewContractABI } from "@zoralabs/protocol-deployments";
"Cannot find module" Errors:
- Ensure the contract package has been built with
build:js - Check that the import path matches the package name exactly
- Verify the ABI is exported from the package's main index file
Missing Addresses:
- Confirm address files exist in the expected location
- Check that JSON keys match the
configKeyexactly (case-sensitive) - Ensure addresses are valid hex strings with
0xprefix
Build Order Issues:
- Always build the source package before the generation package
- Use turbo to handle dependency ordering automatically
- Check that
^build:jsdependencies are correct in turbo.json
Outdated ABIs:
- Clear turbo cache with
turbo run build:js --forceif needed - Ensure contract compilation produced fresh ABIs
- Verify the contract name matches between Solidity and wagmi.config.ts
This systematic approach ensures your contracts become available to external wagmi consumers while maintaining the clean, dependency-free architecture of the protocol-deployments package.
- Follow the established script pattern with
build,build:js,build:contracts:minimal - Add package exports to the internal generation package's
wagmi.config.ts - Update the internal generation package's
package.jsondependencies - Ensure proper tsup configuration for TypeScript builds
- Follow the foundry.toml + wagmi.config.ts pattern for contract packages
- Update turbo.json with correct dependency chains
- Test with
pnpm turbo run build:js --dry-runto verify order - Check documentation builds still work
- Verify CI pipeline changes
- Prefer
build:jsfor non-release workflows - Use explicit forge flags instead of Foundry profiles
- Keep contract dependencies minimal
- Consider build time impact when adding new dependencies
This architecture has evolved to support efficient development while maintaining backward compatibility and enabling external consumption through wagmi. The key insight is the separation of concerns between full contract development builds and consumption-focused builds for TypeScript tooling.
{ "build": "forge build", // Full compilation "build:contracts:minimal": "forge build --skip test --skip script --no-metadata", "wagmi:generate": "pnpm run build:contracts:minimal && wagmi generate && pnpm exec rename-generated-abi-casing", "build:js": "pnpm run wagmi:generate && pnpm run copy-abis && pnpm run prettier:write && tsup", }