{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://evmnow.github.io/contract-metadata/v1/schema.json",
  "title": "Contract Metadata",
  "description": "Human-readable context for smart contracts. Enriches ABI data with titles, descriptions, semantic type annotations, and presentation hints.",
  "type": "object",
  "required": ["$schema", "chainId", "address"],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string",
      "format": "uri",
      "description": "URI pointing to the JSON Schema that this document conforms to."
    },
    "chainId": {
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991,
      "description": "The chain ID of the network where the contract is deployed."
    },
    "address": {
      "type": "string",
      "pattern": "^0x[0-9a-f]{40}$",
      "description": "The contract address (lowercase, without checksum). Use this for a specific deployed contract."
    },
    "includes": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^(interface:[a-z0-9-]+|https://.+)$"
      },
      "description": "Interface identifiers to include (e.g. \"interface:erc20\", \"interface:erc721\") or https URLs. Included metadata is merged with contract-specific metadata taking priority."
    },
    "meta": {
      "$ref": "#/$defs/DocumentMeta",
      "description": "Document-level housekeeping: revision tracking and locale."
    },
    "name": {
      "type": "string",
      "description": "Human-readable contract name (ERC-7572: name)."
    },
    "symbol": {
      "type": "string",
      "description": "Contract or token symbol (ERC-7572: symbol)."
    },
    "description": {
      "type": "string",
      "maxLength": 120,
      "description": "One short sentence describing the contract (ERC-7572: description). Put long-form context in about."
    },
    "image": {
      "type": "string",
      "format": "uri",
      "description": "Contract image or logo URI (ERC-7572: image)."
    },
    "banner_image": {
      "type": "string",
      "format": "uri",
      "description": "Banner image URI (ERC-7572: banner_image)."
    },
    "featured_image": {
      "type": "string",
      "format": "uri",
      "description": "Featured image URI (ERC-7572: featured_image)."
    },
    "external_link": {
      "type": "string",
      "format": "uri",
      "description": "Primary external URL for the project (ERC-7572: external_link)."
    },
    "collaborators": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^0x[0-9a-f]{40}$"
      },
      "description": "Ethereum addresses of authorized metadata editors, lowercase (ERC-7572: collaborators)."
    },
    "about": {
      "type": "string",
      "description": "Long-form context, history, and explanations in Markdown format."
    },
    "category": {
      "type": "string",
      "description": "Primary category. Standard values: token, nft, defi, governance, bridge, oracle, utility, game, social, identity. Custom values are allowed."
    },
    "tags": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Free-form tags for search and categorization."
    },
    "links": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Link"
      },
      "description": "External links (website, documentation, block explorer, etc.)."
    },
    "risks": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Known risks or caveats users should be aware of."
    },
    "audits": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/AuditReference"
      },
      "description": "Security audit references."
    },
    "theme": {
      "$ref": "#/$defs/Theme",
      "description": "Visual theme for UI rendering. Color model inspired by ENSIP-18."
    },
    "groups": {
      "type": "object",
      "description": "Named groups for organizing functions in the UI. Keys are group identifiers referenced by functions.",
      "propertyNames": {
        "pattern": "^[a-zA-Z_][a-zA-Z0-9_-]*$"
      },
      "additionalProperties": {
        "$ref": "#/$defs/Group"
      }
    },
    "actions": {
      "type": "object",
      "description": "User-facing actions, keyed by free-form identifier (e.g. \"approve\", \"revoke\", \"my-balance\"). Each action references an ABI function via its `function` field and may preset or lock parameters. Multiple actions may target the same function to provide UX variants.",
      "propertyNames": {
        "pattern": "^[a-zA-Z_][a-zA-Z0-9_-]*$"
      },
      "additionalProperties": {
        "$ref": "#/$defs/ActionMeta"
      }
    },
    "events": {
      "type": "object",
      "description": "Per-event metadata. Keys can be a bare ABI name, a full signature for overloads, or a 32-byte topic hash for unverified contracts (e.g. \"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\").",
      "additionalProperties": {
        "$ref": "#/$defs/EventMeta"
      }
    },
    "errors": {
      "type": "object",
      "description": "Per-error metadata. Keys can be a bare ABI name, a full signature for overloads, or a 4-byte selector for unverified contracts (e.g. \"0x08c379a0\").",
      "additionalProperties": {
        "$ref": "#/$defs/ErrorMeta"
      }
    },
    "messages": {
      "type": "object",
      "description": "EIP-712 typed message metadata, keyed by primary type name (e.g. \"Permit\", \"Order\"). Describes off-chain signing flows verified by this contract.",
      "additionalProperties": {
        "$ref": "#/$defs/MessageMeta"
      }
    }
  },
  "patternProperties": {
    "^_[a-zA-Z]": {
      "description": "Extension object. Names must start with _ followed by a letter. Consumers that do not understand a given extension must ignore it."
    }
  },
  "$defs": {
    "DocumentMeta": {
      "type": "object",
      "description": "Document-level housekeeping fields.",
      "additionalProperties": false,
      "properties": {
        "version": {
          "type": "integer",
          "minimum": 1,
          "description": "Metadata revision number. Increment when updating metadata for the same contract."
        },
        "lastUpdated": {
          "type": "string",
          "format": "date-time",
          "description": "ISO 8601 timestamp of the last metadata update."
        },
        "locale": {
          "type": "string",
          "pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$",
          "description": "BCP 47 language tag (e.g. \"en\", \"de\", \"ja\", \"pt-BR\")."
        }
      }
    },
    "Theme": {
      "type": "object",
      "description": "Visual theme colors for UI rendering. Inspired by ENSIP-18. Accent and text colors should maintain a 4.5:1 contrast ratio against the background.",
      "additionalProperties": false,
      "properties": {
        "background": {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$",
          "description": "Background color as a hex string."
        },
        "text": {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$",
          "description": "Primary text color."
        },
        "accent": {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$",
          "description": "Accent / brand color."
        },
        "accentText": {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$",
          "description": "Text color used on accent backgrounds."
        },
        "border": {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$",
          "description": "Border color."
        }
      }
    },
    "Link": {
      "type": "object",
      "required": ["label", "url"],
      "additionalProperties": false,
      "properties": {
        "label": {
          "type": "string",
          "description": "Display text for the link."
        },
        "url": {
          "type": "string",
          "format": "uri",
          "description": "URL target."
        }
      }
    },
    "AuditReference": {
      "type": "object",
      "required": ["auditor", "url", "date"],
      "additionalProperties": false,
      "properties": {
        "auditor": {
          "type": "string",
          "description": "Name of the auditing firm."
        },
        "url": {
          "type": "string",
          "format": "uri",
          "description": "URL to the audit report."
        },
        "date": {
          "type": "string",
          "format": "date",
          "description": "Date of the audit (YYYY-MM-DD)."
        },
        "scope": {
          "type": "string",
          "description": "What the audit covered."
        }
      }
    },
    "Group": {
      "type": "object",
      "required": ["label"],
      "additionalProperties": false,
      "properties": {
        "label": {
          "type": "string",
          "description": "Display label for the group."
        },
        "description": {
          "type": "string",
          "maxLength": 120,
          "description": "One short sentence describing what this group contains."
        },
        "order": {
          "type": "integer",
          "minimum": 0,
          "description": "Display order (lower numbers appear first)."
        }
      }
    },
    "ActionMeta": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
        "^_[a-zA-Z]": {
          "description": "Extension object. Names must start with _ followed by a letter. Consumers that do not understand a given extension must ignore it."
        }
      },
      "properties": {
        "function": {
          "type": "string",
          "description": "Reference to the ABI function this action invokes. Accepts a bare name (e.g. \"approve\"), a full Solidity signature (e.g. \"approve(address,uint256)\") for overloaded functions, or a 4-byte selector (e.g. \"0x095ea7b3\") for unverified contracts. OPTIONAL: when omitted, the action's id (its key in the `actions` object) is used as the reference — so `\"approve\": { ... }` is equivalent to `\"approve\": { \"function\": \"approve\", ... }`. Variant actions whose id differs from the underlying function (e.g. `\"revoke\": { \"function\": \"approve\", ... }`) MUST set this field explicitly."
        },
        "order": {
          "type": "integer",
          "minimum": 0,
          "description": "Display order within the action's group (lower numbers appear first). Actions without an order are sorted after ordered ones."
        },
        "title": {
          "type": "string",
          "description": "Human-readable action name."
        },
        "description": {
          "type": "string",
          "maxLength": 120,
          "description": "One short sentence on what the action does, for end users. Put long-form detail in the contract's about."
        },
        "intent": {
          "type": "string",
          "description": "Template string rendered with formatted parameter values. Use {paramName} placeholders (e.g. \"Transfer {wad} to {dst}\"). Prefix with # to prepend a hash symbol (e.g. \"Buy Punk #{punkIndex}\" renders as \"Buy Punk #7804\"). {value} refers to the transaction value when the action declares a value object and no ABI parameter is named value. Values are formatted using their type before insertion."
        },
        "group": {
          "type": "string",
          "description": "Group key (must match a key in the top-level groups object)."
        },
        "warning": {
          "type": "string",
          "description": "Risk warning displayed to users before interaction."
        },
        "featured": {
          "type": "boolean",
          "description": "Mark as a primary/featured action. Featured actions are highlighted in the UI (e.g. the main mint, swap, or stake action)."
        },
        "hidden": {
          "type": "boolean",
          "description": "Hide this action from the default UI. Use for internal or admin-only actions that clutter the interface. If every authored action referencing a function is hidden, the function itself is hidden."
        },
        "stateMutability": {
          "type": "string",
          "enum": ["view", "pure", "nonpayable", "payable"],
          "description": "Overrides the ABI's stateMutability. Use only to correct legacy ABIs that lack this field (e.g. pre-Byzantium contracts)."
        },
        "params": {
          "type": "object",
          "description": "Parameter metadata, keyed by ABI parameter name or zero-based position (_0, _1, ...). Positional keys resolve regardless of the ABI name; interface files must use them. When both forms describe the same parameter, the name key wins.",
          "additionalProperties": {
            "$ref": "#/$defs/ParamMeta"
          }
        },
        "value": {
          "$ref": "#/$defs/ValueMeta",
          "description": "Metadata for the native currency (msg.value) sent with the call. Only meaningful on payable functions."
        },
        "returns": {
          "type": "object",
          "description": "Return value metadata, keyed by ABI return name or zero-based position (_0, _1, ...).",
          "additionalProperties": {
            "$ref": "#/$defs/ParamMeta"
          }
        },
        "examples": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/ActionExample"
          },
          "description": "Preset examples for quick interaction."
        },
        "related": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Action identifiers (keys in the top-level actions object) of related actions for navigation."
        },
        "deprecated": {
          "type": "string",
          "description": "Deprecation notice. If set, the action should be de-emphasized in the UI."
        }
      }
    },
    "MessageMeta": {
      "type": "object",
      "description": "Metadata for an EIP-712 typed message verified by this contract.",
      "additionalProperties": false,
      "patternProperties": {
        "^_[a-zA-Z]": {
          "description": "Extension object. Names must start with _ followed by a letter. Consumers that do not understand a given extension must ignore it."
        }
      },
      "properties": {
        "order": {
          "type": "integer",
          "minimum": 0,
          "description": "Display order (lower numbers appear first). Messages without an order are sorted after ordered ones."
        },
        "title": {
          "type": "string",
          "description": "Human-readable name for this message type."
        },
        "description": {
          "type": "string",
          "maxLength": 120,
          "description": "One short sentence on what signing this message does, for end users."
        },
        "intent": {
          "type": "string",
          "description": "Template string rendered with formatted field values. Use {fieldName} placeholders (e.g. \"Permit {spender} to spend {value}\")."
        },
        "warning": {
          "type": "string",
          "description": "Risk warning displayed to users before signing."
        },
        "fields": {
          "type": "object",
          "description": "Field metadata, keyed by EIP-712 field name.",
          "additionalProperties": {
            "$ref": "#/$defs/ParamMeta"
          }
        }
      }
    },
    "EventMeta": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
        "^_[a-zA-Z]": {
          "description": "Extension object. Names must start with _ followed by a letter. Consumers that do not understand a given extension must ignore it."
        }
      },
      "properties": {
        "order": {
          "type": "integer",
          "minimum": 0,
          "description": "Display order (lower numbers appear first). Events without an order are sorted after ordered ones."
        },
        "title": {
          "type": "string",
          "description": "Human-readable event name."
        },
        "description": {
          "type": "string",
          "maxLength": 120,
          "description": "One short sentence on what this event signifies."
        },
        "params": {
          "type": "object",
          "description": "Parameter metadata, keyed by ABI parameter name or zero-based position (_0, _1, ...).",
          "additionalProperties": {
            "$ref": "#/$defs/ParamMeta"
          }
        }
      }
    },
    "ErrorMeta": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
        "^_[a-zA-Z]": {
          "description": "Extension object. Names must start with _ followed by a letter. Consumers that do not understand a given extension must ignore it."
        }
      },
      "properties": {
        "order": {
          "type": "integer",
          "minimum": 0,
          "description": "Display order (lower numbers appear first). Errors without an order are sorted after ordered ones."
        },
        "title": {
          "type": "string",
          "description": "Human-readable error name."
        },
        "description": {
          "type": "string",
          "maxLength": 120,
          "description": "One short sentence on what this error means and when it occurs."
        },
        "params": {
          "type": "object",
          "description": "Parameter metadata, keyed by ABI parameter name or zero-based position (_0, _1, ...).",
          "additionalProperties": {
            "$ref": "#/$defs/ParamMeta"
          }
        }
      }
    },
    "ParamMeta": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
        "^_[a-zA-Z]": {
          "description": "Extension object. Names must start with _ followed by a letter. Consumers that do not understand a given extension must ignore it."
        }
      },
      "properties": {
        "label": {
          "type": "string",
          "description": "Human-readable parameter name."
        },
        "description": {
          "type": "string",
          "maxLength": 120,
          "description": "One short sentence on what this parameter represents."
        },
        "type": {
          "$ref": "#/$defs/Type",
          "description": "Semantic type annotation. Tells consumers what a value represents (for display) and how it should be collected (for input). Note: `type: \"hidden\"` is a display-side hint (don't render the value in read contexts) and is orthogonal to the input-side `hidden` flag below."
        },
        "autofill": {
          "$ref": "#/$defs/Autofill",
          "description": "Pre-populate this input with a value from the specified source."
        },
        "validation": {
          "$ref": "#/$defs/ValidationRule"
        },
        "preview": {
          "$ref": "#/$defs/ParamPreview"
        },
        "hidden": {
          "type": "boolean",
          "description": "When true, do not render an input for this parameter. The `autofill` value is injected at call time. REQUIRES `autofill`. Use for variant actions like `revoke` where a parameter is fixed (e.g. amount = 0)."
        },
        "disabled": {
          "type": "boolean",
          "description": "When true, render the input but make it non-editable. Displays the autofilled value for transparency. REQUIRES `autofill`. Mutually exclusive with `hidden: true`."
        }
      },
      "dependentRequired": {
        "hidden": ["autofill"],
        "disabled": ["autofill"]
      },
      "not": {
        "required": ["hidden", "disabled"],
        "properties": {
          "hidden": { "const": true },
          "disabled": { "const": true }
        }
      }
    },
    "ValueMeta": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
        "^_[a-zA-Z]": {
          "description": "Extension object. Names must start with _ followed by a letter. Consumers that do not understand a given extension must ignore it."
        }
      },
      "properties": {
        "label": {
          "type": "string",
          "description": "Human-readable label for the attached native currency amount."
        },
        "description": {
          "type": "string",
          "maxLength": 120,
          "description": "One short sentence on what the attached amount pays for."
        },
        "autofill": {
          "$ref": "#/$defs/Autofill",
          "description": "Pre-populate the value input. A constant is denominated in wei."
        },
        "validation": {
          "$ref": "#/$defs/ValidationRule"
        },
        "hidden": {
          "type": "boolean",
          "description": "When true, do not render a value input. The `autofill` value is attached at call time. REQUIRES `autofill`."
        },
        "disabled": {
          "type": "boolean",
          "description": "When true, render the value input but make it non-editable (e.g. a fixed mint price). REQUIRES `autofill`. Mutually exclusive with `hidden: true`."
        }
      },
      "dependentRequired": {
        "hidden": ["autofill"],
        "disabled": ["autofill"]
      },
      "not": {
        "required": ["hidden", "disabled"],
        "properties": {
          "hidden": { "const": true },
          "disabled": { "const": true }
        }
      },
      "description": "Metadata for the native currency (msg.value) sent with a payable call. Always denominated in wei and rendered as the eth semantic type."
    },
    "Type": {
      "oneOf": [
        {
          "type": "string",
          "enum": ["eth", "gwei", "timestamp", "address", "boolean", "blocknumber", "duration", "bytes32-utf8", "token-id", "percentage", "basis-points", "token-amount", "date", "datetime", "hidden"],
          "description": "Semantic type. Tells consumers what a value represents and how to render it in both read and write contexts. Amount-like types share one contract (display = raw / 10^decimals, rendered with a symbol): \"eth\" is a raw wei value shown as ETH (decimals 18); \"gwei\" is a raw wei value shown as gwei (decimals 9). Bare \"token-amount\" and \"token-id\" refer to the contract the document describes; decimals and symbol are resolved on-chain from the token, never configured."
        },
        {
          "type": "object",
          "required": ["type"],
          "additionalProperties": false,
          "properties": {
            "type": { "const": "address" },
            "ens": {
              "type": "boolean",
              "default": true,
              "description": "Enable ENS name resolution. Default: true."
            },
            "addressBook": {
              "type": "boolean",
              "default": false,
              "description": "Show address book picker. Default: false."
            }
          },
          "description": "Address with configurable capabilities."
        },
        {
          "type": "object",
          "required": ["type"],
          "additionalProperties": false,
          "properties": {
            "type": { "const": "token-amount" },
            "tokenAddress": {
              "type": "string",
              "pattern": "^0x[0-9a-f]{40}$",
              "description": "The token contract address (lowercase). Mutually exclusive with tokenParam."
            },
            "tokenParam": {
              "type": "string",
              "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$",
              "description": "Name or positional key (_N) of an address parameter in the same action, event, or message whose value identifies the token. Mutually exclusive with tokenAddress."
            }
          },
          "oneOf": [
            {
              "required": ["tokenAddress"],
              "properties": { "tokenAddress": true }
            },
            {
              "required": ["tokenParam"],
              "properties": { "tokenParam": true }
            }
          ],
          "not": {
            "required": ["tokenAddress", "tokenParam"],
            "properties": { "tokenAddress": true, "tokenParam": true }
          },
          "description": "Token amount for a specific ERC-20, ERC-721, or ERC-1155, identified by a fixed tokenAddress or by the value of a sibling parameter (tokenParam). tokenAddress and tokenParam are mutually exclusive. Without either, use the bare \"token-amount\" string form, which refers to the described contract. Display: format with on-chain decimals and symbol. Input: show balance and max button."
        },
        {
          "type": "object",
          "required": ["type"],
          "additionalProperties": false,
          "properties": {
            "type": { "const": "amount" },
            "decimals": {
              "type": "integer",
              "minimum": 0,
              "default": 18,
              "description": "Fixed-point decimals. Display value = raw / 10^decimals. Default: 18."
            },
            "symbol": {
              "type": "string",
              "description": "Unit label shown after the value (e.g. \"USD\"). Optional."
            }
          },
          "description": "Generic fixed-point amount with no asset identity (oracle prices, share prices, accounting units). For native ETH use \"eth\"; for a real token use \"token-amount\"."
        },
        {
          "type": "object",
          "required": ["type", "tokenAddress"],
          "additionalProperties": false,
          "properties": {
            "type": { "const": "token-id" },
            "tokenAddress": {
              "type": "string",
              "pattern": "^0x[0-9a-f]{40}$",
              "description": "The token contract address (lowercase)."
            }
          },
          "description": "Token ID for a specific ERC-721 or ERC-1155."
        },
        {
          "type": "object",
          "required": ["type", "values"],
          "additionalProperties": false,
          "properties": {
            "type": { "const": "enum" },
            "values": {
              "type": "object",
              "additionalProperties": { "type": "string" },
              "description": "Mapping of raw values to human-readable labels."
            }
          },
          "description": "Enumerated values with labels. Display: show label. Input: render as select dropdown."
        },
        {
          "type": "object",
          "required": ["type", "min", "max"],
          "additionalProperties": false,
          "properties": {
            "type": { "const": "slider" },
            "min": {
              "type": "string",
              "description": "Minimum value (string to support uint256 range)."
            },
            "max": {
              "type": "string",
              "description": "Maximum value (string to support uint256 range)."
            },
            "step": {
              "type": "string",
              "description": "Step increment (string to support uint256 range)."
            }
          },
          "description": "Ranged numeric value. Input: render as slider."
        }
      ],
      "description": "Semantic type annotation. Tells consumers what a value represents and how to render it for both display and input."
    },
    "Autofill": {
      "oneOf": [
        {
          "type": "string",
          "enum": ["connected-address", "contract-address", "zero-address", "block-timestamp"],
          "description": "Well-known autofill source."
        },
        {
          "type": "object",
          "required": ["type", "value"],
          "additionalProperties": false,
          "properties": {
            "type": { "const": "constant" },
            "value": {
              "type": "string",
              "description": "Literal value to pre-populate."
            }
          },
          "description": "Pre-populate with a literal constant value."
        }
      ],
      "description": "Value source for pre-populating an input field."
    },
    "ValidationRule": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "min": {
          "type": "string",
          "description": "Minimum value (as string to support uint256 range)."
        },
        "max": {
          "type": "string",
          "description": "Maximum value (as string to support uint256 range)."
        },
        "enum": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["value", "label"],
            "additionalProperties": false,
            "properties": {
              "value": { "type": "string" },
              "label": { "type": "string" }
            }
          },
          "description": "Allowed values with labels."
        },
        "pattern": {
          "type": "string",
          "description": "Regular expression pattern the value must match."
        },
        "message": {
          "type": "string",
          "description": "Custom validation error message."
        }
      }
    },
    "ParamPreview": {
      "type": "object",
      "description": "Visual preview shown as the user fills in a parameter value.",
      "additionalProperties": false,
      "properties": {
        "image": {
          "type": "string",
          "description": "URI template that resolves to an image for the current parameter value. Supports CAIP-19/CAIP-29, IPFS, and HTTPS URIs. Use {paramName} for interpolation."
        }
      }
    },
    "ActionExample": {
      "type": "object",
      "required": ["label", "params"],
      "additionalProperties": false,
      "properties": {
        "label": {
          "type": "string",
          "description": "Description of the example."
        },
        "params": {
          "type": "object",
          "additionalProperties": { "type": "string" },
          "description": "Parameter values for the example, keyed by ABI parameter name or zero-based position (_0, _1, ...)."
        }
      }
    }
  }
}
