Developing NPM Package for Smart Contracts
Contract is deployed, frontend developer wants to work with it. Option one — manually copy ABI JSON into the project, write calls through ethers.Contract with any type casting. Option two — npm package with typed wrappers that's imported in one line and gives IDE autocomplete.
The difference between options is especially felt during contract upgrade: in the first case you need to find all places where the old ABI is used; in the second — update the package version.
What Should Be Inside the Package
A minimally useful package for a smart contract includes:
ABI as TypeScript constant. Not just JSON file, but typed constant generated through typechain or viem's parseAbi. With TypeChain we get fully typed wrapper classes — MyContract__factory.connect(address, signer) — with autocomplete for all functions, events, errors.
Deployment addresses by networks. Object like { [chainId: number]: string }, collected from Hardhat or Foundry deployment artifacts. When contract is deployed in Arbitrum, Optimism, and Polygon — frontend gets address in one line: ADDRESSES[chainId].
Utilities for formatting. Helpers specific to the contract: parsing events, formatting values (e.g., formatUnits with correct decimals), conversion between on-chain representation and UI.
TypeScript types for all structures. Input parameters, return values, events — all typed. Errors compile with full description.
Package Build and Publication
Build stack: tsup or rollup. tsup is simpler to configure, supports dual CJS/ESM output out of the box:
{
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}
Dual format is important: Next.js with App Router requires ESM, old CRA projects might require CJS. Without dual output the package works in only half of environments.
CI/CD for Publication
GitHub Actions: on tag push v* — run tests → build → publish to npm. Versioning through semantic-release or manual via semver. Main rule: major version changes on any breaking change in ABI (function rename, parameter change).
For internal (closed) packages — GitHub Packages or Verdaccio self-hosted. Configuration .npmrc in client project:
@myorg:registry=https://npm.pkg.github.com
Integration with Foundry and Hardhat
Hardhat automatically generates artifacts in artifacts/contracts/. TypeChain picks them up and generates types. You need to add to pipeline: compile → typechain → build package → publish.
With Foundry — via forge build, artifacts in out/. TypeChain supports Foundry format through @typechain/hardhat with artifacts directory specified. Alternative — wagmi cli generate with foundry plugin, which generates React hooks directly from Foundry artifacts.
Timeline
Developing npm package for one contract with ABI, types, addresses, and basic utilities — 1 working day. Full package with TypeChain typing, dual ESM/CJS build, CI/CD, and documentation — 2-3 days.







