# template-ethereum-contracts: a Hardhat v3 monorepo for upgradeable contracts

> This template wires Hardhat v3 to hardhat-deploy v2 and the rocketh deployment system, with hot contract replacement, proxy deployment and deploy scripts written as plain TypeScript. The contracts live in a pnpm workspace package that a web frontend can import, and a Zellij layout runs the node, the watchers and the tests together.

**wighawag/template-ethereum-contracts** — Template to develop ethereum smart contracts

- Repository: https://github.com/wighawag/template-ethereum-contracts
- Stars: 533 · Forks: 120
- Language: Solidity
- License: Unlicense
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/wighawag-template-ethereum-contracts

## hardhat-deploy and rocketh instead of Ignition

The template is described as a production-ready template for developing EVM smart contracts using Hardhat v3 and hardhat-deploy v2 with the rocketh deployment system. The README is explicit about the choice it makes against Hardhat's official Ignition plugin. Ignition offers a deployment system of its own, but the template's argument is that Ignition comes with a rigid DSL that limits flexibility. Four things are claimed in exchange. Hot Contract Replacement, described as the equivalent of HMR for smart contracts, where you edit a contract and see the change live while developing an app or a game, implemented with proxy patterns and a set of conventions. Deployment logic written in plain TypeScript. Declarative proxy deployment through `deployViaProxy` for upgradeable contracts. And universal deploy scripts, thanks to rocketh, so the same script can run in any environment, node or browser.

## A pnpm workspace where contracts are a package

The layout is a monorepo, and the README says why: to add a web frontend in a separate `web/` folder, to import contract artifacts, ABIs and types from the `contracts` package, to share deployment information across packages, and to publish contracts as an npm package for external consumption. The tree in the README shows the contracts package carrying the Solidity sources, the deploy scripts, per-network deployment artifacts, auto-generated artifacts and ABIs, and a `rocketh/` directory with the configuration, deploy setup and environment setup:

The root has a `package.json` for the monorepo and a `pnpm-workspace.yaml`. The repository top level is deliberately small: a `.github/` directory, the licence, the README, the `contracts/` package, the workspace files and a `pnpm-lock.yaml`.

## The commands are all pnpm scripts that delegate into the workspace

The prerequisites are Node.js v18 or newer and pnpm, and the install is one command:

```bash
pnpm i
```

Everything else is a root script that filters down to the contracts package, so you always work from the monorepo root. Compiling:

```bash
pnpm contracts:compile
```

Running the tests:

```bash
pnpm contracts:test
```

Starting a local Ethereum node:

```bash
pnpm contracts:node:local
```

And deploying to localhost:

```bash
pnpm contracts:deploy localhost --skip-prompts
```

The package manifest shows the mechanism. Each root script runs `ldenv` around a `pnpm run --filter ./contracts <task>`, and the `@@MODE @@` placeholder is what carries a mode argument such as the network name or `--skip-prompts` down to the package. There is also a `preinstall` script running `npx only-allow pnpm`, so npm or yarn installs are refused outright rather than producing a subtly broken workspace. The same shape covers the other tasks, with root scripts for verification, for executing a script against a deployed contract and for running one against a forked environment. The README also recommends Zellij for the development experience, and `pnpm start` launches the local node, auto-compilation, auto-deployment, auto-testing and an interactive shell for running scripts.

## Named accounts are indices, and that is the first thing to get wrong

Accounts are configured in `contracts/rocketh/config.ts`, where the `deployer` is the first account from the mnemonic and the `admin` is the second. For a testnet deployment that is fine. For a production deployment, relying on a positional index means the security of the contract depends on which account the mnemonic produces, and if the mnemonic order ever differs between the machine you tested on and the machine you deploy from, the deployer and the admin swap. The README does not discuss this, so the configuration is yours to tighten. Environment variables for sensitive data go in `.env.local`, with the pattern `MNEMONIC_<network>` and an `ETHERSCAN_API_KEY` for verification, and the README notes you can use Hardhat's secret store instead. Networks are configured in `contracts/hardhat.config.ts` with helper functions, including `addNetworksFromEnv()` to auto-configure networks from `ETH_NODE_URI_*` environment variables, `addNetworksFromKnownList()` for well-known networks, and `addForkConfiguration()` to enable forking mode through the `HARDHAT_FORK` env var.

## Rocketh extensions compose deploy, proxy and viem into one config object

Extensions provide the deployment functionality, and they are also configured in `contracts/rocketh/config.ts`. The README lists four: `@rocketh/deploy` for the basic deploy function, `@rocketh/proxy` for `deployViaProxy` on upgradeable contracts, `@rocketh/read-execute` for the read and execute helpers, and `@rocketh/viem` for the viem client integration. The composition is a plain object spread, so adding or removing a capability is a matter of including or dropping an extension. That matters for a deploy script, because the script calls `env.deployViaProxy` and `env.viem.getContract`, and those methods exist because the corresponding extensions are present. If you remove the proxy extension, upgradeable deployment stops being available; if you remove the viem extension, the script has no client. The rocketh design also means the same deployment code can run against a local node, a fork, or a public network without change, which is the point the README makes about universal deploy scripts.

## Deploy scripts are plain TypeScript functions that run in order

Deploy scripts live in `contracts/deploy/` and are executed in order, prefixed with numbers so the sequence is explicit rather than inferred. Each one is a `deployScript` wrapping an async function that receives the environment. The function destructures `env.namedAccounts`, then calls `env.deployViaProxy` with the contract name, the artifact, the constructor arguments and a third object holding the owner and the linked data stored with the deployment. It then reads back from the deployed contract through the viem client. The README's example deploys a contract named GreetingsRegistry with the constructor argument `prefix:`, and the trailing options object carries a `tags` array, which is how a deployment is selected by tag so you can deploy one contract rather than the whole set. The example also shows the fork and execute variants of the same idea, with `pnpm contracts:execute` running a script against a deployed contract and `pnpm contracts:fork:execute` running it against a forked environment.

## Where template-ethereum-contracts is the wrong choice

Three limits are concrete. The first is the stack size. This template assumes Hardhat v3, hardhat-deploy v2, rocketh, viem, pnpm workspaces, TypeScript deploy scripts, forge-style Solidity tests using `forge-std` alongside TypeScript tests using the Node.js test runner with `earl`, and Zellij for the development layout. A team that only wants to compile and test a contract does not need any of that, and Hardhat alone would do. The second is that the proxy story is opinionated. Hot contract replacement and `deployViaProxy` are the centre of the template, and if your contracts are not upgradeable, or you are on a chain where the proxy pattern you inherit is not what you want, that central piece is weight you carry for nothing. The third is that the template ships with a licence status worth checking. The repository metadata reports the licence as Unlicense, and the repository does have a LICENSE file, but a template is something teams copy and then diverge from, so the question of what governs the copy is one to settle early rather than at audit time.

## The alternative is plain Hardhat, and the difference is what you build yourself

The realistic alternative is to start from a bare Hardhat project and add only what you need, which is what many teams do. The difference in approach is where the deployment logic lives. In this template, deployment is a set of TypeScript scripts with explicit ordering, named accounts, proxy conventions and rocketh extensions, and the trade is that you inherit a set of opinions about upgrades and about how a contract's deployment artifacts are recorded. With plain Hardhat, your deployment script is a file you write, you can use the Ignition module system or a plain script, and there is nothing to unlearn later. The case for the template is speed and a working set of upgradeable-contract conventions, and the case against it is that a team which does not need upgradeability, a frontend package or a local fork workflow ends up with a monorepo, a workspace file, a Zellij layout and a proxy layer it never uses.

## Conclusion

template-ethereum-contracts fits a team shipping upgradeable EVM contracts that wants a deployment story already wired for proxy patterns, live redeployment and a frontend importing the same artifacts. It is the wrong choice if you want Hardhat Ignition and its module system, since the README's argument against it is precisely that the Ignition DSL is rigid, and the wrong choice if your project is a single contract with no frontend. Before you adopt it, run `pnpm contracts:test` and `pnpm contracts:compile` on a clean checkout, and read the account configuration in `contracts/rocketh/config.ts`, since named account indices are where a deployment goes wrong quietly.

## FAQ

### How do I install template-ethereum-contracts?

You need Node.js v18 or newer and pnpm, then run `pnpm i` in the repository root. The root package.json has a `preinstall` script running `npx only-allow pnpm`, so npm and yarn installs are refused.

### How do I compile and test the contracts?

Run `pnpm contracts:compile` to compile both Solidity and TypeScript, and `pnpm contracts:test` to run both the Solidity tests, which are forge-style using `forge-std`, and the TypeScript tests, which use the Node.js test runner with `earl` assertions.

### How are deploy scripts written and ordered?

Deploy scripts live in `contracts/deploy/` and are executed in order because they are prefixed with numbers. Each is a `deployScript` wrapping an async function that receives the environment, with named accounts available from `env.namedAccounts`.

### How do I deploy an upgradeable contract with template-ethereum-contracts?

Call `env.deployViaProxy` in a deploy script with the contract name, artifact, arguments and linked data, as shown in the README example deploying GreetingsRegistry. The proxy capability comes from the `@rocketh/proxy` extension listed in `contracts/rocketh/config.ts`.

## Sources

- [Issues](https://github.com/wighawag/template-ethereum-contracts/issues)
- [License: Unlicense](https://github.com/wighawag/template-ethereum-contracts/blob/main/LICENSE)
- [README](https://github.com/wighawag/template-ethereum-contracts/blob/main/README.md)
- [wighawag/template-ethereum-contracts on GitHub](https://github.com/wighawag/template-ethereum-contracts)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/wighawag-template-ethereum-contracts
