- Create the file at
contracts/{chainId}/{address}.json:- The directory is the decimal chain ID (
1for Ethereum mainnet). - The filename is the contract address, lowercase, no checksum — the validator rejects files whose name doesn't match the
addressfield or whose directory doesn't matchchainId.
- The directory is the decimal chain ID (
- Set the header fields:
$schemaMUST be exactlyhttps://evmnow.github.io/contract-metadata/v1/schema.json.chainIdandaddressare required;addressmust be lowercase hex.
- Keep every
description(contract, action, event, error, message, group, parameter) to a single plain-language sentence of at most 120 characters. Long-form context belongs in the contract-levelaboutfield (Markdown, no length limit). - Do not invent data. Only add
audits,links,risks, etc. that are real and verifiable. Fictional/showcase documents belong inexamples/. - Run the validator (see below) until it passes with zero errors.
Includes are merged shallowly per action key: if your contract file declares an action that an included interface also provides, your object replaces the interface's object entirely — nothing is inherited field-by-field.
When you override an interface action (e.g. approve from interface:erc20):
- Re-declare
titleif the interface had one. - Re-declare
params(all of them, not just the ones you change). - Re-declare
returns. - Re-declare
warning(dropping a risk warning silently is the worst case). - Re-declare
stateMutabilityif the interface set it. - Re-declare
related. - Re-declare
group(andintent) if you want the action to stay grouped.
The validator enforces this: an override that drops title, params, returns, warning, stateMutability, or related compared to the interface version fails strict validation.
Interface files describe every implementation of a standard, so:
$schemaMUST be exactlyhttps://evmnow.github.io/contract-metadata/v1/interface.schema.json.paramsandreturnskeys MUST be positional (_0,_1, ...) — parameter names are not part of a function's interface (approve(address,uint256)is the same function whether the parameters are namedspender/amountorguy/wad). This is a MUST in the spec and enforced by the validator.- Declare
interfaceId(the ERC-165 identifier, e.g.0x36372b07for ERC-20) so consumers can auto-detect the interface viasupportsInterface(). - Interface files MAY declare
includesto compose other interfaces; the same merge semantics apply and cycles are rejected. - Keep files small and single-purpose: ERC-721 core, metadata, and enumerable extensions are separate files (
erc721.json,erc721-metadata.json,erc721-enumerable.json) so contracts only include what they implement.
Extension fields start with _ followed by a letter (e.g. _component). To document a well-known extension:
- Add
extensions/<name>.mddescribing the extension's purpose and shape. - Add
extensions/<name>.schema.json— a JSON Schema for the extension's value. The pair is the convention: every documented extension has both files. - If the validator should enforce it, wire it up in
validate.ts(see the_componenthandling).
Consumers that do not understand an extension MUST ignore it.
pnpm install
pnpm validate # everything: contracts/, schema/interfaces/, examples/
pnpm validate:contracts # only contracts/
pnpm validate:interfaces # only schema/interfaces/
pnpm validate:examples # only examples/
pnpm typecheck # typecheck validate.tsValidation is strict by default: schema violations, layout errors, semantic issues (unknown groups, dangling related references, hidden without autofill, ambiguous variants), and shallow-merge completeness all fail the run. --no-strict downgrades the semantic class to warnings if you need a partial report while iterating, but CI runs strict.