Ship typed ABIs for viem consumers - #9
Conversation
viem and abitype read event and function names out of the ABI type. A JSON import widens every "type": "uint256" to string, which no longer satisfies Abi, so a consumer lost every name and had to keep its own hand-written copy of the ABI and a drift check against this package. Each release now also ships a typed module per ABI set: `<release>/abis` for the contracts and `<release>/abis/merged` for the call surfaces. The declaration carries the literal type. The runtime file re-exports the JSON the release already ships, so the ABI bytes stay in the package once however many modules name them. Packing writes the modules next to the ABIs they read, and the audit compares them against that JSON, so a declaration cannot outlive the contract it describes. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 92859a4. Configure here.
| const releaseDir = resolveReleaseDir(entry) | ||
| if (releaseDir === undefined || entry.releaseDir === undefined) { | ||
| throw new Error(`Release ${entry.releaseId} has no packed directory`) | ||
| } |
There was a problem hiding this comment.
Audit throws without packed directory
Medium Severity
auditRelease always calls auditTypedAbis, and generateTypedAbisForRelease throws when releaseDir is missing instead of returning a finding. auditPackedRelease already reports that case, so a release without a packed directory now crashes the whole audit, including auditAll for other releases.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 92859a4. Configure here.
There was a problem hiding this comment.
Good catch, fixed.
auditTypedAbis now returns no findings when the release has no packed directory or no manifest. auditPackedRelease already reports both cases, so adding a second finding would only have been noise, and the throw meant one unpacked release ended the audit of every other one.
Covered by a test that builds a config with a release that has artifactsDir but no releaseDir and asserts auditRelease reports release has no packed directory rather than throwing. Removing the guard makes it fail.
auditTypedAbis generated the modules to compare them, and generating throws when the release has nothing packed. auditPackedRelease already reports that case, so the throw ended the audit of every other release instead of adding anything. Co-authored-by: Cursor <cursoragent@cursor.com>


Why
viem and abitype read event and function names out of the ABI type. A JSON import widens every
"type": "uint256"tostring, which no longer satisfiesAbi, and every name goes with it. A consumer that wanted the names had to keep a hand-writtenas constcopy of the ABI plus a drift check against this package.What
Each release ships two more modules:
<release>/abis<release>/abis/mergedThe declaration carries the literal type. The runtime file re-exports the JSON the release already ships, so the ABI bytes appear once in the package however many modules name them:
merged.jsis 8 lines.Keeping them honest
packwrites the modules alongside the ABIs they read, so a release cannot ship one without the other.auditregenerates them and compares, so a declaration left behind by a repack fails CI. Verified by hand: editing one event name in a declaration turns the audit red.Size
The declarations add about 1 MB to the package, the largest single module being 438 KB. Typechecking a consumer that imports the merged surface takes about 1.5s.
Checks
npm test(60 tests),lint:ci,format:ci,buildandauditall pass.Made with Cursor